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