LCOV - code coverage report
Current view: top level - src/backend/utils/adt - cash.c (source / functions) Hit Total Coverage
Test: PostgreSQL 17devel Lines: 341 413 82.6 %
Date: 2024-04-20 07:11:39 Functions: 35 37 94.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * cash.c
       3             :  * Written by D'Arcy J.M. Cain
       4             :  * darcy@druid.net
       5             :  * http://www.druid.net/darcy/
       6             :  *
       7             :  * Functions to allow input and output of money normally but store
       8             :  * and handle it as 64 bit ints
       9             :  *
      10             :  * A slightly modified version of this file and a discussion of the
      11             :  * workings can be found in the book "Software Solutions in C" by
      12             :  * Dale Schumacher, Academic Press, ISBN: 0-12-632360-7 except that
      13             :  * this version handles 64 bit numbers and so can hold values up to
      14             :  * $92,233,720,368,547,758.07.
      15             :  *
      16             :  * src/backend/utils/adt/cash.c
      17             :  */
      18             : 
      19             : #include "postgres.h"
      20             : 
      21             : #include <limits.h>
      22             : #include <ctype.h>
      23             : #include <math.h>
      24             : 
      25             : #include "common/int.h"
      26             : #include "libpq/pqformat.h"
      27             : #include "utils/builtins.h"
      28             : #include "utils/cash.h"
      29             : #include "utils/numeric.h"
      30             : #include "utils/pg_locale.h"
      31             : 
      32             : 
      33             : /*************************************************************************
      34             :  * Private routines
      35             :  ************************************************************************/
      36             : 
      37             : static const char *
      38          24 : num_word(Cash value)
      39             : {
      40             :     static char buf[128];
      41             :     static const char *const small[] = {
      42             :         "zero", "one", "two", "three", "four", "five", "six", "seven",
      43             :         "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
      44             :         "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
      45             :         "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
      46             :     };
      47          24 :     const char *const *big = small + 18;
      48          24 :     int         tu = value % 100;
      49             : 
      50             :     /* deal with the simple cases first */
      51          24 :     if (value <= 20)
      52           6 :         return small[value];
      53             : 
      54             :     /* is it an even multiple of 100? */
      55          18 :     if (!tu)
      56             :     {
      57           0 :         sprintf(buf, "%s hundred", small[value / 100]);
      58           0 :         return buf;
      59             :     }
      60             : 
      61             :     /* more than 99? */
      62          18 :     if (value > 99)
      63             :     {
      64             :         /* is it an even multiple of 10 other than 10? */
      65          12 :         if (value % 10 == 0 && tu > 10)
      66           0 :             sprintf(buf, "%s hundred %s",
      67           0 :                     small[value / 100], big[tu / 10]);
      68          12 :         else if (tu < 20)
      69           0 :             sprintf(buf, "%s hundred and %s",
      70           0 :                     small[value / 100], small[tu]);
      71             :         else
      72          12 :             sprintf(buf, "%s hundred %s %s",
      73          12 :                     small[value / 100], big[tu / 10], small[tu % 10]);
      74             :     }
      75             :     else
      76             :     {
      77             :         /* is it an even multiple of 10 other than 10? */
      78           6 :         if (value % 10 == 0 && tu > 10)
      79           0 :             sprintf(buf, "%s", big[tu / 10]);
      80           6 :         else if (tu < 20)
      81           0 :             sprintf(buf, "%s", small[tu]);
      82             :         else
      83           6 :             sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
      84             :     }
      85             : 
      86          18 :     return buf;
      87             : }                               /* num_word() */
      88             : 
      89             : /* cash_in()
      90             :  * Convert a string to a cash data type.
      91             :  * Format is [$]###[,]###[.##]
      92             :  * Examples: 123.45 $123.45 $123,456.78
      93             :  *
      94             :  */
      95             : Datum
      96        1540 : cash_in(PG_FUNCTION_ARGS)
      97             : {
      98        1540 :     char       *str = PG_GETARG_CSTRING(0);
      99        1540 :     Node       *escontext = fcinfo->context;
     100             :     Cash        result;
     101        1540 :     Cash        value = 0;
     102        1540 :     Cash        dec = 0;
     103        1540 :     Cash        sgn = 1;
     104        1540 :     bool        seen_dot = false;
     105        1540 :     const char *s = str;
     106             :     int         fpoint;
     107             :     char        dsymbol;
     108             :     const char *ssymbol,
     109             :                *psymbol,
     110             :                *nsymbol,
     111             :                *csymbol;
     112        1540 :     struct lconv *lconvert = PGLC_localeconv();
     113             : 
     114             :     /*
     115             :      * frac_digits will be CHAR_MAX in some locales, notably C.  However, just
     116             :      * testing for == CHAR_MAX is risky, because of compilers like gcc that
     117             :      * "helpfully" let you alter the platform-standard definition of whether
     118             :      * char is signed or not.  If we are so unfortunate as to get compiled
     119             :      * with a nonstandard -fsigned-char or -funsigned-char switch, then our
     120             :      * idea of CHAR_MAX will not agree with libc's. The safest course is not
     121             :      * to test for CHAR_MAX at all, but to impose a range check for plausible
     122             :      * frac_digits values.
     123             :      */
     124        1540 :     fpoint = lconvert->frac_digits;
     125        1540 :     if (fpoint < 0 || fpoint > 10)
     126        1540 :         fpoint = 2;             /* best guess in this case, I think */
     127             : 
     128             :     /* we restrict dsymbol to be a single byte, but not the other symbols */
     129        1540 :     if (*lconvert->mon_decimal_point != '\0' &&
     130           0 :         lconvert->mon_decimal_point[1] == '\0')
     131           0 :         dsymbol = *lconvert->mon_decimal_point;
     132             :     else
     133        1540 :         dsymbol = '.';
     134        1540 :     if (*lconvert->mon_thousands_sep != '\0')
     135           0 :         ssymbol = lconvert->mon_thousands_sep;
     136             :     else                        /* ssymbol should not equal dsymbol */
     137        1540 :         ssymbol = (dsymbol != ',') ? "," : ".";
     138        1540 :     csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
     139        1540 :     psymbol = (*lconvert->positive_sign != '\0') ? lconvert->positive_sign : "+";
     140        1540 :     nsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
     141             : 
     142             : #ifdef CASHDEBUG
     143             :     printf("cashin- precision '%d'; decimal '%c'; thousands '%s'; currency '%s'; positive '%s'; negative '%s'\n",
     144             :            fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
     145             : #endif
     146             : 
     147             :     /* we need to add all sorts of checking here.  For now just */
     148             :     /* strip all leading whitespace and any leading currency symbol */
     149        1540 :     while (isspace((unsigned char) *s))
     150           0 :         s++;
     151        1540 :     if (strncmp(s, csymbol, strlen(csymbol)) == 0)
     152         120 :         s += strlen(csymbol);
     153        1540 :     while (isspace((unsigned char) *s))
     154           0 :         s++;
     155             : 
     156             : #ifdef CASHDEBUG
     157             :     printf("cashin- string is '%s'\n", s);
     158             : #endif
     159             : 
     160             :     /* a leading minus or paren signifies a negative number */
     161             :     /* again, better heuristics needed */
     162             :     /* XXX - doesn't properly check for balanced parens - djmc */
     163        1540 :     if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
     164             :     {
     165         610 :         sgn = -1;
     166         610 :         s += strlen(nsymbol);
     167             :     }
     168         930 :     else if (*s == '(')
     169             :     {
     170          12 :         sgn = -1;
     171          12 :         s++;
     172             :     }
     173         918 :     else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
     174           0 :         s += strlen(psymbol);
     175             : 
     176             : #ifdef CASHDEBUG
     177             :     printf("cashin- string is '%s'\n", s);
     178             : #endif
     179             : 
     180             :     /* allow whitespace and currency symbol after the sign, too */
     181        1540 :     while (isspace((unsigned char) *s))
     182           0 :         s++;
     183        1540 :     if (strncmp(s, csymbol, strlen(csymbol)) == 0)
     184           6 :         s += strlen(csymbol);
     185        1540 :     while (isspace((unsigned char) *s))
     186           0 :         s++;
     187             : 
     188             : #ifdef CASHDEBUG
     189             :     printf("cashin- string is '%s'\n", s);
     190             : #endif
     191             : 
     192             :     /*
     193             :      * We accumulate the absolute amount in "value" and then apply the sign at
     194             :      * the end.  (The sign can appear before or after the digits, so it would
     195             :      * be more complicated to do otherwise.)  Because of the larger range of
     196             :      * negative signed integers, we build "value" in the negative and then
     197             :      * flip the sign at the end, catching most-negative-number overflow if
     198             :      * necessary.
     199             :      */
     200             : 
     201       15184 :     for (; *s; s++)
     202             :     {
     203             :         /*
     204             :          * We look for digits as long as we have found less than the required
     205             :          * number of decimal places.
     206             :          */
     207       13728 :         if (isdigit((unsigned char) *s) && (!seen_dot || dec < fpoint))
     208       12238 :         {
     209       12256 :             int8        digit = *s - '0';
     210             : 
     211       24500 :             if (pg_mul_s64_overflow(value, 10, &value) ||
     212       12244 :                 pg_sub_s64_overflow(value, digit, &value))
     213          18 :                 ereturn(escontext, (Datum) 0,
     214             :                         (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     215             :                          errmsg("value \"%s\" is out of range for type %s",
     216             :                                 str, "money")));
     217             : 
     218       12238 :             if (seen_dot)
     219        2782 :                 dec++;
     220             :         }
     221             :         /* decimal point? then start counting fractions... */
     222        1472 :         else if (*s == dsymbol && !seen_dot)
     223             :         {
     224        1400 :             seen_dot = true;
     225             :         }
     226             :         /* ignore if "thousands" separator, else we're done */
     227          72 :         else if (strncmp(s, ssymbol, strlen(ssymbol)) == 0)
     228           6 :             s += strlen(ssymbol) - 1;
     229             :         else
     230          66 :             break;
     231             :     }
     232             : 
     233             :     /* round off if there's another digit */
     234        1522 :     if (isdigit((unsigned char) *s) && *s >= '5')
     235             :     {
     236             :         /* remember we build the value in the negative */
     237          30 :         if (pg_sub_s64_overflow(value, 1, &value))
     238           6 :             ereturn(escontext, (Datum) 0,
     239             :                     (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     240             :                      errmsg("value \"%s\" is out of range for type %s",
     241             :                             str, "money")));
     242             :     }
     243             : 
     244             :     /* adjust for less than required decimal places */
     245        1760 :     for (; dec < fpoint; dec++)
     246             :     {
     247         268 :         if (pg_mul_s64_overflow(value, 10, &value))
     248          24 :             ereturn(escontext, (Datum) 0,
     249             :                     (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     250             :                      errmsg("value \"%s\" is out of range for type %s",
     251             :                             str, "money")));
     252             :     }
     253             : 
     254             :     /*
     255             :      * should only be trailing digits followed by whitespace, right paren,
     256             :      * trailing sign, and/or trailing currency symbol
     257             :      */
     258        1528 :     while (isdigit((unsigned char) *s))
     259          36 :         s++;
     260             : 
     261        1504 :     while (*s)
     262             :     {
     263          24 :         if (isspace((unsigned char) *s) || *s == ')')
     264          12 :             s++;
     265          12 :         else if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
     266             :         {
     267           0 :             sgn = -1;
     268           0 :             s += strlen(nsymbol);
     269             :         }
     270          12 :         else if (strncmp(s, psymbol, strlen(psymbol)) == 0)
     271           0 :             s += strlen(psymbol);
     272          12 :         else if (strncmp(s, csymbol, strlen(csymbol)) == 0)
     273           0 :             s += strlen(csymbol);
     274             :         else
     275          12 :             ereturn(escontext, (Datum) 0,
     276             :                     (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
     277             :                      errmsg("invalid input syntax for type %s: \"%s\"",
     278             :                             "money", str)));
     279             :     }
     280             : 
     281             :     /*
     282             :      * If the value is supposed to be positive, flip the sign, but check for
     283             :      * the most negative number.
     284             :      */
     285        1480 :     if (sgn > 0)
     286             :     {
     287         882 :         if (value == PG_INT64_MIN)
     288          12 :             ereturn(escontext, (Datum) 0,
     289             :                     (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
     290             :                      errmsg("value \"%s\" is out of range for type %s",
     291             :                             str, "money")));
     292         870 :         result = -value;
     293             :     }
     294             :     else
     295         598 :         result = value;
     296             : 
     297             : #ifdef CASHDEBUG
     298             :     printf("cashin- result is " INT64_FORMAT "\n", result);
     299             : #endif
     300             : 
     301        1468 :     PG_RETURN_CASH(result);
     302             : }
     303             : 
     304             : 
     305             : /* cash_out()
     306             :  * Function to convert cash to a dollars and cents representation, using
     307             :  * the lc_monetary locale's formatting.
     308             :  */
     309             : Datum
     310         416 : cash_out(PG_FUNCTION_ARGS)
     311             : {
     312         416 :     Cash        value = PG_GETARG_CASH(0);
     313             :     char       *result;
     314             :     char        buf[128];
     315             :     char       *bufptr;
     316             :     int         digit_pos;
     317             :     int         points,
     318             :                 mon_group;
     319             :     char        dsymbol;
     320             :     const char *ssymbol,
     321             :                *csymbol,
     322             :                *signsymbol;
     323             :     char        sign_posn,
     324             :                 cs_precedes,
     325             :                 sep_by_space;
     326         416 :     struct lconv *lconvert = PGLC_localeconv();
     327             : 
     328             :     /* see comments about frac_digits in cash_in() */
     329         416 :     points = lconvert->frac_digits;
     330         416 :     if (points < 0 || points > 10)
     331         416 :         points = 2;             /* best guess in this case, I think */
     332             : 
     333             :     /*
     334             :      * As with frac_digits, must apply a range check to mon_grouping to avoid
     335             :      * being fooled by variant CHAR_MAX values.
     336             :      */
     337         416 :     mon_group = *lconvert->mon_grouping;
     338         416 :     if (mon_group <= 0 || mon_group > 6)
     339         416 :         mon_group = 3;
     340             : 
     341             :     /* we restrict dsymbol to be a single byte, but not the other symbols */
     342         416 :     if (*lconvert->mon_decimal_point != '\0' &&
     343           0 :         lconvert->mon_decimal_point[1] == '\0')
     344           0 :         dsymbol = *lconvert->mon_decimal_point;
     345             :     else
     346         416 :         dsymbol = '.';
     347         416 :     if (*lconvert->mon_thousands_sep != '\0')
     348           0 :         ssymbol = lconvert->mon_thousands_sep;
     349             :     else                        /* ssymbol should not equal dsymbol */
     350         416 :         ssymbol = (dsymbol != ',') ? "," : ".";
     351         416 :     csymbol = (*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$";
     352             : 
     353         416 :     if (value < 0)
     354             :     {
     355             :         /* make the amount positive for digit-reconstruction loop */
     356          86 :         value = -value;
     357             :         /* set up formatting data */
     358          86 :         signsymbol = (*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-";
     359          86 :         sign_posn = lconvert->n_sign_posn;
     360          86 :         cs_precedes = lconvert->n_cs_precedes;
     361          86 :         sep_by_space = lconvert->n_sep_by_space;
     362             :     }
     363             :     else
     364             :     {
     365         330 :         signsymbol = lconvert->positive_sign;
     366         330 :         sign_posn = lconvert->p_sign_posn;
     367         330 :         cs_precedes = lconvert->p_cs_precedes;
     368         330 :         sep_by_space = lconvert->p_sep_by_space;
     369             :     }
     370             : 
     371             :     /* we build the digits+decimal-point+sep string right-to-left in buf[] */
     372         416 :     bufptr = buf + sizeof(buf) - 1;
     373         416 :     *bufptr = '\0';
     374             : 
     375             :     /*
     376             :      * Generate digits till there are no non-zero digits left and we emitted
     377             :      * at least one to the left of the decimal point.  digit_pos is the
     378             :      * current digit position, with zero as the digit just left of the decimal
     379             :      * point, increasing to the right.
     380             :      */
     381         416 :     digit_pos = points;
     382             :     do
     383             :     {
     384        3288 :         if (points && digit_pos == 0)
     385             :         {
     386             :             /* insert decimal point, but not if value cannot be fractional */
     387         416 :             *(--bufptr) = dsymbol;
     388             :         }
     389        2872 :         else if (digit_pos < 0 && (digit_pos % mon_group) == 0)
     390             :         {
     391             :             /* insert thousands sep, but only to left of radix point */
     392         530 :             bufptr -= strlen(ssymbol);
     393         530 :             memcpy(bufptr, ssymbol, strlen(ssymbol));
     394             :         }
     395             : 
     396        3288 :         *(--bufptr) = ((uint64) value % 10) + '0';
     397        3288 :         value = ((uint64) value) / 10;
     398        3288 :         digit_pos--;
     399        3288 :     } while (value || digit_pos >= 0);
     400             : 
     401             :     /*----------
     402             :      * Now, attach currency symbol and sign symbol in the correct order.
     403             :      *
     404             :      * The POSIX spec defines these values controlling this code:
     405             :      *
     406             :      * p/n_sign_posn:
     407             :      *  0   Parentheses enclose the quantity and the currency_symbol.
     408             :      *  1   The sign string precedes the quantity and the currency_symbol.
     409             :      *  2   The sign string succeeds the quantity and the currency_symbol.
     410             :      *  3   The sign string precedes the currency_symbol.
     411             :      *  4   The sign string succeeds the currency_symbol.
     412             :      *
     413             :      * p/n_cs_precedes: 0 means currency symbol after value, else before it.
     414             :      *
     415             :      * p/n_sep_by_space:
     416             :      *  0   No <space> separates the currency symbol and value.
     417             :      *  1   If the currency symbol and sign string are adjacent, a <space>
     418             :      *      separates them from the value; otherwise, a <space> separates
     419             :      *      the currency symbol from the value.
     420             :      *  2   If the currency symbol and sign string are adjacent, a <space>
     421             :      *      separates them; otherwise, a <space> separates the sign string
     422             :      *      from the value.
     423             :      *----------
     424             :      */
     425         416 :     switch (sign_posn)
     426             :     {
     427           0 :         case 0:
     428           0 :             if (cs_precedes)
     429           0 :                 result = psprintf("(%s%s%s)",
     430             :                                   csymbol,
     431             :                                   (sep_by_space == 1) ? " " : "",
     432             :                                   bufptr);
     433             :             else
     434           0 :                 result = psprintf("(%s%s%s)",
     435             :                                   bufptr,
     436             :                                   (sep_by_space == 1) ? " " : "",
     437             :                                   csymbol);
     438           0 :             break;
     439         416 :         case 1:
     440             :         default:
     441         416 :             if (cs_precedes)
     442         416 :                 result = psprintf("%s%s%s%s%s",
     443             :                                   signsymbol,
     444             :                                   (sep_by_space == 2) ? " " : "",
     445             :                                   csymbol,
     446             :                                   (sep_by_space == 1) ? " " : "",
     447             :                                   bufptr);
     448             :             else
     449           0 :                 result = psprintf("%s%s%s%s%s",
     450             :                                   signsymbol,
     451             :                                   (sep_by_space == 2) ? " " : "",
     452             :                                   bufptr,
     453             :                                   (sep_by_space == 1) ? " " : "",
     454             :                                   csymbol);
     455         416 :             break;
     456           0 :         case 2:
     457           0 :             if (cs_precedes)
     458           0 :                 result = psprintf("%s%s%s%s%s",
     459             :                                   csymbol,
     460             :                                   (sep_by_space == 1) ? " " : "",
     461             :                                   bufptr,
     462             :                                   (sep_by_space == 2) ? " " : "",
     463             :                                   signsymbol);
     464             :             else
     465           0 :                 result = psprintf("%s%s%s%s%s",
     466             :                                   bufptr,
     467             :                                   (sep_by_space == 1) ? " " : "",
     468             :                                   csymbol,
     469             :                                   (sep_by_space == 2) ? " " : "",
     470             :                                   signsymbol);
     471           0 :             break;
     472           0 :         case 3:
     473           0 :             if (cs_precedes)
     474           0 :                 result = psprintf("%s%s%s%s%s",
     475             :                                   signsymbol,
     476             :                                   (sep_by_space == 2) ? " " : "",
     477             :                                   csymbol,
     478             :                                   (sep_by_space == 1) ? " " : "",
     479             :                                   bufptr);
     480             :             else
     481           0 :                 result = psprintf("%s%s%s%s%s",
     482             :                                   bufptr,
     483             :                                   (sep_by_space == 1) ? " " : "",
     484             :                                   signsymbol,
     485             :                                   (sep_by_space == 2) ? " " : "",
     486             :                                   csymbol);
     487           0 :             break;
     488           0 :         case 4:
     489           0 :             if (cs_precedes)
     490           0 :                 result = psprintf("%s%s%s%s%s",
     491             :                                   csymbol,
     492             :                                   (sep_by_space == 2) ? " " : "",
     493             :                                   signsymbol,
     494             :                                   (sep_by_space == 1) ? " " : "",
     495             :                                   bufptr);
     496             :             else
     497           0 :                 result = psprintf("%s%s%s%s%s",
     498             :                                   bufptr,
     499             :                                   (sep_by_space == 1) ? " " : "",
     500             :                                   csymbol,
     501             :                                   (sep_by_space == 2) ? " " : "",
     502             :                                   signsymbol);
     503           0 :             break;
     504             :     }
     505             : 
     506         416 :     PG_RETURN_CSTRING(result);
     507             : }
     508             : 
     509             : /*
     510             :  *      cash_recv           - converts external binary format to cash
     511             :  */
     512             : Datum
     513           0 : cash_recv(PG_FUNCTION_ARGS)
     514             : {
     515           0 :     StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
     516             : 
     517           0 :     PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
     518             : }
     519             : 
     520             : /*
     521             :  *      cash_send           - converts cash to binary format
     522             :  */
     523             : Datum
     524           0 : cash_send(PG_FUNCTION_ARGS)
     525             : {
     526           0 :     Cash        arg1 = PG_GETARG_CASH(0);
     527             :     StringInfoData buf;
     528             : 
     529           0 :     pq_begintypsend(&buf);
     530           0 :     pq_sendint64(&buf, arg1);
     531           0 :     PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
     532             : }
     533             : 
     534             : /*
     535             :  * Comparison functions
     536             :  */
     537             : 
     538             : Datum
     539        1100 : cash_eq(PG_FUNCTION_ARGS)
     540             : {
     541        1100 :     Cash        c1 = PG_GETARG_CASH(0);
     542        1100 :     Cash        c2 = PG_GETARG_CASH(1);
     543             : 
     544        1100 :     PG_RETURN_BOOL(c1 == c2);
     545             : }
     546             : 
     547             : Datum
     548          12 : cash_ne(PG_FUNCTION_ARGS)
     549             : {
     550          12 :     Cash        c1 = PG_GETARG_CASH(0);
     551          12 :     Cash        c2 = PG_GETARG_CASH(1);
     552             : 
     553          12 :     PG_RETURN_BOOL(c1 != c2);
     554             : }
     555             : 
     556             : Datum
     557        1098 : cash_lt(PG_FUNCTION_ARGS)
     558             : {
     559        1098 :     Cash        c1 = PG_GETARG_CASH(0);
     560        1098 :     Cash        c2 = PG_GETARG_CASH(1);
     561             : 
     562        1098 :     PG_RETURN_BOOL(c1 < c2);
     563             : }
     564             : 
     565             : Datum
     566        1098 : cash_le(PG_FUNCTION_ARGS)
     567             : {
     568        1098 :     Cash        c1 = PG_GETARG_CASH(0);
     569        1098 :     Cash        c2 = PG_GETARG_CASH(1);
     570             : 
     571        1098 :     PG_RETURN_BOOL(c1 <= c2);
     572             : }
     573             : 
     574             : Datum
     575        1098 : cash_gt(PG_FUNCTION_ARGS)
     576             : {
     577        1098 :     Cash        c1 = PG_GETARG_CASH(0);
     578        1098 :     Cash        c2 = PG_GETARG_CASH(1);
     579             : 
     580        1098 :     PG_RETURN_BOOL(c1 > c2);
     581             : }
     582             : 
     583             : Datum
     584        1098 : cash_ge(PG_FUNCTION_ARGS)
     585             : {
     586        1098 :     Cash        c1 = PG_GETARG_CASH(0);
     587        1098 :     Cash        c2 = PG_GETARG_CASH(1);
     588             : 
     589        1098 :     PG_RETURN_BOOL(c1 >= c2);
     590             : }
     591             : 
     592             : Datum
     593        1318 : cash_cmp(PG_FUNCTION_ARGS)
     594             : {
     595        1318 :     Cash        c1 = PG_GETARG_CASH(0);
     596        1318 :     Cash        c2 = PG_GETARG_CASH(1);
     597             : 
     598        1318 :     if (c1 > c2)
     599        1122 :         PG_RETURN_INT32(1);
     600         196 :     else if (c1 == c2)
     601          14 :         PG_RETURN_INT32(0);
     602             :     else
     603         182 :         PG_RETURN_INT32(-1);
     604             : }
     605             : 
     606             : 
     607             : /* cash_pl()
     608             :  * Add two cash values.
     609             :  */
     610             : Datum
     611          24 : cash_pl(PG_FUNCTION_ARGS)
     612             : {
     613          24 :     Cash        c1 = PG_GETARG_CASH(0);
     614          24 :     Cash        c2 = PG_GETARG_CASH(1);
     615             :     Cash        result;
     616             : 
     617          24 :     result = c1 + c2;
     618             : 
     619          24 :     PG_RETURN_CASH(result);
     620             : }
     621             : 
     622             : 
     623             : /* cash_mi()
     624             :  * Subtract two cash values.
     625             :  */
     626             : Datum
     627          12 : cash_mi(PG_FUNCTION_ARGS)
     628             : {
     629          12 :     Cash        c1 = PG_GETARG_CASH(0);
     630          12 :     Cash        c2 = PG_GETARG_CASH(1);
     631             :     Cash        result;
     632             : 
     633          12 :     result = c1 - c2;
     634             : 
     635          12 :     PG_RETURN_CASH(result);
     636             : }
     637             : 
     638             : 
     639             : /* cash_div_cash()
     640             :  * Divide cash by cash, returning float8.
     641             :  */
     642             : Datum
     643           6 : cash_div_cash(PG_FUNCTION_ARGS)
     644             : {
     645           6 :     Cash        dividend = PG_GETARG_CASH(0);
     646           6 :     Cash        divisor = PG_GETARG_CASH(1);
     647             :     float8      quotient;
     648             : 
     649           6 :     if (divisor == 0)
     650           0 :         ereport(ERROR,
     651             :                 (errcode(ERRCODE_DIVISION_BY_ZERO),
     652             :                  errmsg("division by zero")));
     653             : 
     654           6 :     quotient = (float8) dividend / (float8) divisor;
     655           6 :     PG_RETURN_FLOAT8(quotient);
     656             : }
     657             : 
     658             : 
     659             : /* cash_mul_flt8()
     660             :  * Multiply cash by float8.
     661             :  */
     662             : Datum
     663           6 : cash_mul_flt8(PG_FUNCTION_ARGS)
     664             : {
     665           6 :     Cash        c = PG_GETARG_CASH(0);
     666           6 :     float8      f = PG_GETARG_FLOAT8(1);
     667             :     Cash        result;
     668             : 
     669           6 :     result = rint(c * f);
     670           6 :     PG_RETURN_CASH(result);
     671             : }
     672             : 
     673             : 
     674             : /* flt8_mul_cash()
     675             :  * Multiply float8 by cash.
     676             :  */
     677             : Datum
     678           6 : flt8_mul_cash(PG_FUNCTION_ARGS)
     679             : {
     680           6 :     float8      f = PG_GETARG_FLOAT8(0);
     681           6 :     Cash        c = PG_GETARG_CASH(1);
     682             :     Cash        result;
     683             : 
     684           6 :     result = rint(f * c);
     685           6 :     PG_RETURN_CASH(result);
     686             : }
     687             : 
     688             : 
     689             : /* cash_div_flt8()
     690             :  * Divide cash by float8.
     691             :  */
     692             : Datum
     693          12 : cash_div_flt8(PG_FUNCTION_ARGS)
     694             : {
     695          12 :     Cash        c = PG_GETARG_CASH(0);
     696          12 :     float8      f = PG_GETARG_FLOAT8(1);
     697             :     Cash        result;
     698             : 
     699          12 :     if (f == 0.0)
     700           0 :         ereport(ERROR,
     701             :                 (errcode(ERRCODE_DIVISION_BY_ZERO),
     702             :                  errmsg("division by zero")));
     703             : 
     704          12 :     result = rint(c / f);
     705          12 :     PG_RETURN_CASH(result);
     706             : }
     707             : 
     708             : 
     709             : /* cash_mul_flt4()
     710             :  * Multiply cash by float4.
     711             :  */
     712             : Datum
     713           6 : cash_mul_flt4(PG_FUNCTION_ARGS)
     714             : {
     715           6 :     Cash        c = PG_GETARG_CASH(0);
     716           6 :     float4      f = PG_GETARG_FLOAT4(1);
     717             :     Cash        result;
     718             : 
     719           6 :     result = rint(c * (float8) f);
     720           6 :     PG_RETURN_CASH(result);
     721             : }
     722             : 
     723             : 
     724             : /* flt4_mul_cash()
     725             :  * Multiply float4 by cash.
     726             :  */
     727             : Datum
     728           6 : flt4_mul_cash(PG_FUNCTION_ARGS)
     729             : {
     730           6 :     float4      f = PG_GETARG_FLOAT4(0);
     731           6 :     Cash        c = PG_GETARG_CASH(1);
     732             :     Cash        result;
     733             : 
     734           6 :     result = rint((float8) f * c);
     735           6 :     PG_RETURN_CASH(result);
     736             : }
     737             : 
     738             : 
     739             : /* cash_div_flt4()
     740             :  * Divide cash by float4.
     741             :  *
     742             :  */
     743             : Datum
     744          12 : cash_div_flt4(PG_FUNCTION_ARGS)
     745             : {
     746          12 :     Cash        c = PG_GETARG_CASH(0);
     747          12 :     float4      f = PG_GETARG_FLOAT4(1);
     748             :     Cash        result;
     749             : 
     750          12 :     if (f == 0.0)
     751           0 :         ereport(ERROR,
     752             :                 (errcode(ERRCODE_DIVISION_BY_ZERO),
     753             :                  errmsg("division by zero")));
     754             : 
     755          12 :     result = rint(c / (float8) f);
     756          12 :     PG_RETURN_CASH(result);
     757             : }
     758             : 
     759             : 
     760             : /* cash_mul_int8()
     761             :  * Multiply cash by int8.
     762             :  */
     763             : Datum
     764           6 : cash_mul_int8(PG_FUNCTION_ARGS)
     765             : {
     766           6 :     Cash        c = PG_GETARG_CASH(0);
     767           6 :     int64       i = PG_GETARG_INT64(1);
     768             :     Cash        result;
     769             : 
     770           6 :     result = c * i;
     771           6 :     PG_RETURN_CASH(result);
     772             : }
     773             : 
     774             : 
     775             : /* int8_mul_cash()
     776             :  * Multiply int8 by cash.
     777             :  */
     778             : Datum
     779           6 : int8_mul_cash(PG_FUNCTION_ARGS)
     780             : {
     781           6 :     int64       i = PG_GETARG_INT64(0);
     782           6 :     Cash        c = PG_GETARG_CASH(1);
     783             :     Cash        result;
     784             : 
     785           6 :     result = i * c;
     786           6 :     PG_RETURN_CASH(result);
     787             : }
     788             : 
     789             : /* cash_div_int8()
     790             :  * Divide cash by 8-byte integer.
     791             :  */
     792             : Datum
     793          18 : cash_div_int8(PG_FUNCTION_ARGS)
     794             : {
     795          18 :     Cash        c = PG_GETARG_CASH(0);
     796          18 :     int64       i = PG_GETARG_INT64(1);
     797             :     Cash        result;
     798             : 
     799          18 :     if (i == 0)
     800           0 :         ereport(ERROR,
     801             :                 (errcode(ERRCODE_DIVISION_BY_ZERO),
     802             :                  errmsg("division by zero")));
     803             : 
     804          18 :     result = c / i;
     805             : 
     806          18 :     PG_RETURN_CASH(result);
     807             : }
     808             : 
     809             : 
     810             : /* cash_mul_int4()
     811             :  * Multiply cash by int4.
     812             :  */
     813             : Datum
     814           6 : cash_mul_int4(PG_FUNCTION_ARGS)
     815             : {
     816           6 :     Cash        c = PG_GETARG_CASH(0);
     817           6 :     int32       i = PG_GETARG_INT32(1);
     818             :     Cash        result;
     819             : 
     820           6 :     result = c * i;
     821           6 :     PG_RETURN_CASH(result);
     822             : }
     823             : 
     824             : 
     825             : /* int4_mul_cash()
     826             :  * Multiply int4 by cash.
     827             :  */
     828             : Datum
     829           6 : int4_mul_cash(PG_FUNCTION_ARGS)
     830             : {
     831           6 :     int32       i = PG_GETARG_INT32(0);
     832           6 :     Cash        c = PG_GETARG_CASH(1);
     833             :     Cash        result;
     834             : 
     835           6 :     result = i * c;
     836           6 :     PG_RETURN_CASH(result);
     837             : }
     838             : 
     839             : 
     840             : /* cash_div_int4()
     841             :  * Divide cash by 4-byte integer.
     842             :  *
     843             :  */
     844             : Datum
     845          18 : cash_div_int4(PG_FUNCTION_ARGS)
     846             : {
     847          18 :     Cash        c = PG_GETARG_CASH(0);
     848          18 :     int32       i = PG_GETARG_INT32(1);
     849             :     Cash        result;
     850             : 
     851          18 :     if (i == 0)
     852           0 :         ereport(ERROR,
     853             :                 (errcode(ERRCODE_DIVISION_BY_ZERO),
     854             :                  errmsg("division by zero")));
     855             : 
     856          18 :     result = c / i;
     857             : 
     858          18 :     PG_RETURN_CASH(result);
     859             : }
     860             : 
     861             : 
     862             : /* cash_mul_int2()
     863             :  * Multiply cash by int2.
     864             :  */
     865             : Datum
     866           6 : cash_mul_int2(PG_FUNCTION_ARGS)
     867             : {
     868           6 :     Cash        c = PG_GETARG_CASH(0);
     869           6 :     int16       s = PG_GETARG_INT16(1);
     870             :     Cash        result;
     871             : 
     872           6 :     result = c * s;
     873           6 :     PG_RETURN_CASH(result);
     874             : }
     875             : 
     876             : /* int2_mul_cash()
     877             :  * Multiply int2 by cash.
     878             :  */
     879             : Datum
     880           6 : int2_mul_cash(PG_FUNCTION_ARGS)
     881             : {
     882           6 :     int16       s = PG_GETARG_INT16(0);
     883           6 :     Cash        c = PG_GETARG_CASH(1);
     884             :     Cash        result;
     885             : 
     886           6 :     result = s * c;
     887           6 :     PG_RETURN_CASH(result);
     888             : }
     889             : 
     890             : /* cash_div_int2()
     891             :  * Divide cash by int2.
     892             :  *
     893             :  */
     894             : Datum
     895          18 : cash_div_int2(PG_FUNCTION_ARGS)
     896             : {
     897          18 :     Cash        c = PG_GETARG_CASH(0);
     898          18 :     int16       s = PG_GETARG_INT16(1);
     899             :     Cash        result;
     900             : 
     901          18 :     if (s == 0)
     902           0 :         ereport(ERROR,
     903             :                 (errcode(ERRCODE_DIVISION_BY_ZERO),
     904             :                  errmsg("division by zero")));
     905             : 
     906          18 :     result = c / s;
     907          18 :     PG_RETURN_CASH(result);
     908             : }
     909             : 
     910             : /* cashlarger()
     911             :  * Return larger of two cash values.
     912             :  */
     913             : Datum
     914           6 : cashlarger(PG_FUNCTION_ARGS)
     915             : {
     916           6 :     Cash        c1 = PG_GETARG_CASH(0);
     917           6 :     Cash        c2 = PG_GETARG_CASH(1);
     918             :     Cash        result;
     919             : 
     920           6 :     result = (c1 > c2) ? c1 : c2;
     921             : 
     922           6 :     PG_RETURN_CASH(result);
     923             : }
     924             : 
     925             : /* cashsmaller()
     926             :  * Return smaller of two cash values.
     927             :  */
     928             : Datum
     929           6 : cashsmaller(PG_FUNCTION_ARGS)
     930             : {
     931           6 :     Cash        c1 = PG_GETARG_CASH(0);
     932           6 :     Cash        c2 = PG_GETARG_CASH(1);
     933             :     Cash        result;
     934             : 
     935           6 :     result = (c1 < c2) ? c1 : c2;
     936             : 
     937           6 :     PG_RETURN_CASH(result);
     938             : }
     939             : 
     940             : /* cash_words()
     941             :  * This converts an int4 as well but to a representation using words
     942             :  * Obviously way North American centric - sorry
     943             :  */
     944             : Datum
     945          12 : cash_words(PG_FUNCTION_ARGS)
     946             : {
     947          12 :     Cash        value = PG_GETARG_CASH(0);
     948             :     uint64      val;
     949             :     char        buf[256];
     950          12 :     char       *p = buf;
     951             :     Cash        m0;
     952             :     Cash        m1;
     953             :     Cash        m2;
     954             :     Cash        m3;
     955             :     Cash        m4;
     956             :     Cash        m5;
     957             :     Cash        m6;
     958             : 
     959             :     /* work with positive numbers */
     960          12 :     if (value < 0)
     961             :     {
     962           0 :         value = -value;
     963           0 :         strcpy(buf, "minus ");
     964           0 :         p += 6;
     965             :     }
     966             :     else
     967          12 :         buf[0] = '\0';
     968             : 
     969             :     /* Now treat as unsigned, to avoid trouble at INT_MIN */
     970          12 :     val = (uint64) value;
     971             : 
     972          12 :     m0 = val % INT64CONST(100); /* cents */
     973          12 :     m1 = (val / INT64CONST(100)) % 1000;    /* hundreds */
     974          12 :     m2 = (val / INT64CONST(100000)) % 1000; /* thousands */
     975          12 :     m3 = (val / INT64CONST(100000000)) % 1000;  /* millions */
     976          12 :     m4 = (val / INT64CONST(100000000000)) % 1000;   /* billions */
     977          12 :     m5 = (val / INT64CONST(100000000000000)) % 1000;    /* trillions */
     978          12 :     m6 = (val / INT64CONST(100000000000000000)) % 1000; /* quadrillions */
     979             : 
     980          12 :     if (m6)
     981             :     {
     982           0 :         strcat(buf, num_word(m6));
     983           0 :         strcat(buf, " quadrillion ");
     984             :     }
     985             : 
     986          12 :     if (m5)
     987             :     {
     988           0 :         strcat(buf, num_word(m5));
     989           0 :         strcat(buf, " trillion ");
     990             :     }
     991             : 
     992          12 :     if (m4)
     993             :     {
     994           0 :         strcat(buf, num_word(m4));
     995           0 :         strcat(buf, " billion ");
     996             :     }
     997             : 
     998          12 :     if (m3)
     999             :     {
    1000           0 :         strcat(buf, num_word(m3));
    1001           0 :         strcat(buf, " million ");
    1002             :     }
    1003             : 
    1004          12 :     if (m2)
    1005             :     {
    1006           0 :         strcat(buf, num_word(m2));
    1007           0 :         strcat(buf, " thousand ");
    1008             :     }
    1009             : 
    1010          12 :     if (m1)
    1011          12 :         strcat(buf, num_word(m1));
    1012             : 
    1013          12 :     if (!*p)
    1014           0 :         strcat(buf, "zero");
    1015             : 
    1016          12 :     strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
    1017          12 :     strcat(buf, num_word(m0));
    1018          12 :     strcat(buf, m0 == 1 ? " cent" : " cents");
    1019             : 
    1020             :     /* capitalize output */
    1021          12 :     buf[0] = pg_toupper((unsigned char) buf[0]);
    1022             : 
    1023             :     /* return as text datum */
    1024          12 :     PG_RETURN_TEXT_P(cstring_to_text(buf));
    1025             : }
    1026             : 
    1027             : 
    1028             : /* cash_numeric()
    1029             :  * Convert cash to numeric.
    1030             :  */
    1031             : Datum
    1032          24 : cash_numeric(PG_FUNCTION_ARGS)
    1033             : {
    1034          24 :     Cash        money = PG_GETARG_CASH(0);
    1035             :     Datum       result;
    1036             :     int         fpoint;
    1037          24 :     struct lconv *lconvert = PGLC_localeconv();
    1038             : 
    1039             :     /* see comments about frac_digits in cash_in() */
    1040          24 :     fpoint = lconvert->frac_digits;
    1041          24 :     if (fpoint < 0 || fpoint > 10)
    1042          24 :         fpoint = 2;
    1043             : 
    1044             :     /* convert the integral money value to numeric */
    1045          24 :     result = NumericGetDatum(int64_to_numeric(money));
    1046             : 
    1047             :     /* scale appropriately, if needed */
    1048          24 :     if (fpoint > 0)
    1049             :     {
    1050             :         int64       scale;
    1051             :         int         i;
    1052             :         Datum       numeric_scale;
    1053             :         Datum       quotient;
    1054             : 
    1055             :         /* compute required scale factor */
    1056          24 :         scale = 1;
    1057          72 :         for (i = 0; i < fpoint; i++)
    1058          48 :             scale *= 10;
    1059          24 :         numeric_scale = NumericGetDatum(int64_to_numeric(scale));
    1060             : 
    1061             :         /*
    1062             :          * Given integral inputs approaching INT64_MAX, select_div_scale()
    1063             :          * might choose a result scale of zero, causing loss of fractional
    1064             :          * digits in the quotient.  We can ensure an exact result by setting
    1065             :          * the dscale of either input to be at least as large as the desired
    1066             :          * result scale.  numeric_round() will do that for us.
    1067             :          */
    1068          24 :         numeric_scale = DirectFunctionCall2(numeric_round,
    1069             :                                             numeric_scale,
    1070             :                                             Int32GetDatum(fpoint));
    1071             : 
    1072             :         /* Now we can safely divide ... */
    1073          24 :         quotient = DirectFunctionCall2(numeric_div, result, numeric_scale);
    1074             : 
    1075             :         /* ... and forcibly round to exactly the intended number of digits */
    1076          24 :         result = DirectFunctionCall2(numeric_round,
    1077             :                                      quotient,
    1078             :                                      Int32GetDatum(fpoint));
    1079             :     }
    1080             : 
    1081          24 :     PG_RETURN_DATUM(result);
    1082             : }
    1083             : 
    1084             : /* numeric_cash()
    1085             :  * Convert numeric to cash.
    1086             :  */
    1087             : Datum
    1088          12 : numeric_cash(PG_FUNCTION_ARGS)
    1089             : {
    1090          12 :     Datum       amount = PG_GETARG_DATUM(0);
    1091             :     Cash        result;
    1092             :     int         fpoint;
    1093             :     int64       scale;
    1094             :     int         i;
    1095             :     Datum       numeric_scale;
    1096          12 :     struct lconv *lconvert = PGLC_localeconv();
    1097             : 
    1098             :     /* see comments about frac_digits in cash_in() */
    1099          12 :     fpoint = lconvert->frac_digits;
    1100          12 :     if (fpoint < 0 || fpoint > 10)
    1101          12 :         fpoint = 2;
    1102             : 
    1103             :     /* compute required scale factor */
    1104          12 :     scale = 1;
    1105          36 :     for (i = 0; i < fpoint; i++)
    1106          24 :         scale *= 10;
    1107             : 
    1108             :     /* multiply the input amount by scale factor */
    1109          12 :     numeric_scale = NumericGetDatum(int64_to_numeric(scale));
    1110          12 :     amount = DirectFunctionCall2(numeric_mul, amount, numeric_scale);
    1111             : 
    1112             :     /* note that numeric_int8 will round to nearest integer for us */
    1113          12 :     result = DatumGetInt64(DirectFunctionCall1(numeric_int8, amount));
    1114             : 
    1115          12 :     PG_RETURN_CASH(result);
    1116             : }
    1117             : 
    1118             : /* int4_cash()
    1119             :  * Convert int4 (int) to cash
    1120             :  */
    1121             : Datum
    1122          42 : int4_cash(PG_FUNCTION_ARGS)
    1123             : {
    1124          42 :     int32       amount = PG_GETARG_INT32(0);
    1125             :     Cash        result;
    1126             :     int         fpoint;
    1127             :     int64       scale;
    1128             :     int         i;
    1129          42 :     struct lconv *lconvert = PGLC_localeconv();
    1130             : 
    1131             :     /* see comments about frac_digits in cash_in() */
    1132          42 :     fpoint = lconvert->frac_digits;
    1133          42 :     if (fpoint < 0 || fpoint > 10)
    1134          42 :         fpoint = 2;
    1135             : 
    1136             :     /* compute required scale factor */
    1137          42 :     scale = 1;
    1138         126 :     for (i = 0; i < fpoint; i++)
    1139          84 :         scale *= 10;
    1140             : 
    1141             :     /* compute amount * scale, checking for overflow */
    1142          42 :     result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
    1143             :                                                Int64GetDatum(scale)));
    1144             : 
    1145          42 :     PG_RETURN_CASH(result);
    1146             : }
    1147             : 
    1148             : /* int8_cash()
    1149             :  * Convert int8 (bigint) to cash
    1150             :  */
    1151             : Datum
    1152          24 : int8_cash(PG_FUNCTION_ARGS)
    1153             : {
    1154          24 :     int64       amount = PG_GETARG_INT64(0);
    1155             :     Cash        result;
    1156             :     int         fpoint;
    1157             :     int64       scale;
    1158             :     int         i;
    1159          24 :     struct lconv *lconvert = PGLC_localeconv();
    1160             : 
    1161             :     /* see comments about frac_digits in cash_in() */
    1162          24 :     fpoint = lconvert->frac_digits;
    1163          24 :     if (fpoint < 0 || fpoint > 10)
    1164          24 :         fpoint = 2;
    1165             : 
    1166             :     /* compute required scale factor */
    1167          24 :     scale = 1;
    1168          72 :     for (i = 0; i < fpoint; i++)
    1169          48 :         scale *= 10;
    1170             : 
    1171             :     /* compute amount * scale, checking for overflow */
    1172          24 :     result = DatumGetInt64(DirectFunctionCall2(int8mul, Int64GetDatum(amount),
    1173             :                                                Int64GetDatum(scale)));
    1174             : 
    1175          24 :     PG_RETURN_CASH(result);
    1176             : }

Generated by: LCOV version 1.14