]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/cash.c
Add support for dividing money by money (yielding a float8 result) and for
[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  * $PostgreSQL: pgsql/src/backend/utils/adt/cash.c,v 1.83 2010/07/16 02:15:53 tgl Exp $
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/numeric.h"
30 #include "utils/pg_locale.h"
31
32 #define CASH_BUFSZ              36
33
34 #define TERMINATOR              (CASH_BUFSZ - 1)
35 #define LAST_PAREN              (TERMINATOR - 1)
36 #define LAST_DIGIT              (LAST_PAREN - 1)
37
38
39 /*************************************************************************
40  * Private routines
41  ************************************************************************/
42
43 static const char *
44 num_word(Cash value)
45 {
46         static char buf[128];
47         static const char *small[] = {
48                 "zero", "one", "two", "three", "four", "five", "six", "seven",
49                 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
50                 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
51                 "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
52         };
53         const char **big = small + 18;
54         int                     tu = value % 100;
55
56         /* deal with the simple cases first */
57         if (value <= 20)
58                 return small[value];
59
60         /* is it an even multiple of 100? */
61         if (!tu)
62         {
63                 sprintf(buf, "%s hundred", small[value / 100]);
64                 return buf;
65         }
66
67         /* more than 99? */
68         if (value > 99)
69         {
70                 /* is it an even multiple of 10 other than 10? */
71                 if (value % 10 == 0 && tu > 10)
72                         sprintf(buf, "%s hundred %s",
73                                         small[value / 100], big[tu / 10]);
74                 else if (tu < 20)
75                         sprintf(buf, "%s hundred and %s",
76                                         small[value / 100], small[tu]);
77                 else
78                         sprintf(buf, "%s hundred %s %s",
79                                         small[value / 100], big[tu / 10], small[tu % 10]);
80         }
81         else
82         {
83                 /* is it an even multiple of 10 other than 10? */
84                 if (value % 10 == 0 && tu > 10)
85                         sprintf(buf, "%s", big[tu / 10]);
86                 else if (tu < 20)
87                         sprintf(buf, "%s", small[tu]);
88                 else
89                         sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
90         }
91
92         return buf;
93 }       /* num_word() */
94
95
96 /* cash_in()
97  * Convert a string to a cash data type.
98  * Format is [$]###[,]###[.##]
99  * Examples: 123.45 $123.45 $123,456.78
100  *
101  */
102 Datum
103 cash_in(PG_FUNCTION_ARGS)
104 {
105         char       *str = PG_GETARG_CSTRING(0);
106         Cash            result;
107         Cash            value = 0;
108         Cash            dec = 0;
109         Cash            sgn = 1;
110         int                     seen_dot = 0;
111         const char *s = str;
112         int                     fpoint;
113         char            dsymbol,
114                                 ssymbol,
115                                 psymbol;
116         const char *nsymbol,
117                            *csymbol;
118         struct lconv *lconvert = PGLC_localeconv();
119
120         /*
121          * frac_digits will be CHAR_MAX in some locales, notably C.  However, just
122          * testing for == CHAR_MAX is risky, because of compilers like gcc that
123          * "helpfully" let you alter the platform-standard definition of whether
124          * char is signed or not.  If we are so unfortunate as to get compiled
125          * with a nonstandard -fsigned-char or -funsigned-char switch, then our
126          * idea of CHAR_MAX will not agree with libc's. The safest course is not
127          * to test for CHAR_MAX at all, but to impose a range check for plausible
128          * frac_digits values.
129          */
130         fpoint = lconvert->frac_digits;
131         if (fpoint < 0 || fpoint > 10)
132                 fpoint = 2;                             /* best guess in this case, I think */
133
134         dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
135         if (*lconvert->mon_thousands_sep != '\0')
136                 ssymbol = *lconvert->mon_thousands_sep;
137         else
138                 /* ssymbol should not equal dsymbol */
139                 ssymbol = (dsymbol != ',') ? ',' : '.';
140         csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
141         psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
142         nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
143
144 #ifdef CASHDEBUG
145         printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
146                    fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
147 #endif
148
149         /* we need to add all sorts of checking here.  For now just */
150         /* strip all leading whitespace and any leading currency symbol */
151         while (isspace((unsigned char) *s))
152                 s++;
153         if (strncmp(s, csymbol, strlen(csymbol)) == 0)
154                 s += strlen(csymbol);
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 #ifdef CASHDEBUG
168                 printf("cashin- negative symbol; string is '%s'\n", s);
169 #endif
170         }
171         else if (*s == '(')
172         {
173                 sgn = -1;
174                 s++;
175         }
176         else if (*s == psymbol)
177                 s++;
178
179 #ifdef CASHDEBUG
180         printf("cashin- string is '%s'\n", s);
181 #endif
182
183         while (isspace((unsigned char) *s))
184                 s++;
185         if (strncmp(s, csymbol, strlen(csymbol)) == 0)
186                 s += strlen(csymbol);
187
188 #ifdef CASHDEBUG
189         printf("cashin- string is '%s'\n", s);
190 #endif
191
192         for (;; 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 = 1;
207                 }
208                 /* ignore if "thousands" separator, else we're done */
209                 else if (*s != ssymbol)
210                 {
211                         /* round off */
212                         if (isdigit((unsigned char) *s) && *s >= '5')
213                                 value++;
214
215                         /* adjust for less than required decimal places */
216                         for (; dec < fpoint; dec++)
217                                 value *= 10;
218
219                         break;
220                 }
221         }
222
223         /* should only be trailing digits followed by whitespace or right paren */
224         while (isdigit((unsigned char) *s))
225                 s++;
226         while (isspace((unsigned char) *s) || *s == ')')
227                 s++;
228
229         if (*s != '\0')
230                 ereport(ERROR,
231                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
232                                  errmsg("invalid input syntax for type money: \"%s\"", str)));
233
234         result = value * sgn;
235
236 #ifdef CASHDEBUG
237         printf("cashin- result is " INT64_FORMAT "\n", result);
238 #endif
239
240         PG_RETURN_CASH(result);
241 }
242
243
244 /* cash_out()
245  * Function to convert cash to a dollars and cents representation.
246  * XXX HACK This code appears to assume US conventions for
247  *      positive-valued amounts. - tgl 97/04/14
248  */
249 Datum
250 cash_out(PG_FUNCTION_ARGS)
251 {
252         Cash            value = PG_GETARG_CASH(0);
253         char       *result;
254         char            buf[CASH_BUFSZ];
255         int                     minus = 0;
256         int                     count = LAST_DIGIT;
257         int                     point_pos;
258         int                     ssymbol_position = 0;
259         int                     points,
260                                 mon_group;
261         char            ssymbol;
262         const char *csymbol,
263                            *nsymbol;
264         char            dsymbol;
265         char            convention;
266         struct lconv *lconvert = PGLC_localeconv();
267
268         /* see comments about frac_digits in cash_in() */
269         points = lconvert->frac_digits;
270         if (points < 0 || points > 10)
271                 points = 2;                             /* best guess in this case, I think */
272
273         /*
274          * As with frac_digits, must apply a range check to mon_grouping to avoid
275          * being fooled by variant CHAR_MAX values.
276          */
277         mon_group = *lconvert->mon_grouping;
278         if (mon_group <= 0 || mon_group > 6)
279                 mon_group = 3;
280
281         convention = lconvert->n_sign_posn;
282         dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
283         if (*lconvert->mon_thousands_sep != '\0')
284                 ssymbol = *lconvert->mon_thousands_sep;
285         else
286                 /* ssymbol should not equal dsymbol */
287                 ssymbol = (dsymbol != ',') ? ',' : '.';
288         csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
289         nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
290
291         point_pos = LAST_DIGIT - points;
292
293         point_pos -= (points - 1) / mon_group;
294         ssymbol_position = point_pos % (mon_group + 1);
295
296         /* we work with positive amounts and add the minus sign at the end */
297         if (value < 0)
298         {
299                 minus = 1;
300                 value = -value;
301         }
302
303         /* allow for trailing negative strings */
304         MemSet(buf, ' ', CASH_BUFSZ);
305         buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
306
307         while (value || count > (point_pos - 2))
308         {
309                 if (points && count == point_pos)
310                         buf[count--] = dsymbol;
311                 else if (ssymbol && count % (mon_group + 1) == ssymbol_position)
312                         buf[count--] = ssymbol;
313
314                 buf[count--] = ((uint64) value % 10) + '0';
315                 value = ((uint64) value) / 10;
316         }
317
318         strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
319         count -= strlen(csymbol) - 1;
320
321         /*
322          * If points == 0 and the number of digits % mon_group == 0, the code
323          * above adds a trailing ssymbol on the far right, so remove it.
324          */
325         if (buf[LAST_DIGIT] == ssymbol)
326                 buf[LAST_DIGIT] = '\0';
327
328         /* see if we need to signify negative amount */
329         if (minus)
330         {
331                 result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
332
333                 /* Position code of 0 means use parens */
334                 if (convention == 0)
335                         sprintf(result, "(%s)", buf + count);
336                 else if (convention == 2)
337                         sprintf(result, "%s%s", buf + count, nsymbol);
338                 else
339                         sprintf(result, "%s%s", nsymbol, buf + count);
340         }
341         else
342         {
343                 result = palloc(CASH_BUFSZ + 2 - count);
344                 strcpy(result, buf + count);
345         }
346
347         PG_RETURN_CSTRING(result);
348 }
349
350 /*
351  *              cash_recv                       - converts external binary format to cash
352  */
353 Datum
354 cash_recv(PG_FUNCTION_ARGS)
355 {
356         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
357
358         PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
359 }
360
361 /*
362  *              cash_send                       - converts cash to binary format
363  */
364 Datum
365 cash_send(PG_FUNCTION_ARGS)
366 {
367         Cash            arg1 = PG_GETARG_CASH(0);
368         StringInfoData buf;
369
370         pq_begintypsend(&buf);
371         pq_sendint64(&buf, arg1);
372         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
373 }
374
375 /*
376  * Comparison functions
377  */
378
379 Datum
380 cash_eq(PG_FUNCTION_ARGS)
381 {
382         Cash            c1 = PG_GETARG_CASH(0);
383         Cash            c2 = PG_GETARG_CASH(1);
384
385         PG_RETURN_BOOL(c1 == c2);
386 }
387
388 Datum
389 cash_ne(PG_FUNCTION_ARGS)
390 {
391         Cash            c1 = PG_GETARG_CASH(0);
392         Cash            c2 = PG_GETARG_CASH(1);
393
394         PG_RETURN_BOOL(c1 != c2);
395 }
396
397 Datum
398 cash_lt(PG_FUNCTION_ARGS)
399 {
400         Cash            c1 = PG_GETARG_CASH(0);
401         Cash            c2 = PG_GETARG_CASH(1);
402
403         PG_RETURN_BOOL(c1 < c2);
404 }
405
406 Datum
407 cash_le(PG_FUNCTION_ARGS)
408 {
409         Cash            c1 = PG_GETARG_CASH(0);
410         Cash            c2 = PG_GETARG_CASH(1);
411
412         PG_RETURN_BOOL(c1 <= c2);
413 }
414
415 Datum
416 cash_gt(PG_FUNCTION_ARGS)
417 {
418         Cash            c1 = PG_GETARG_CASH(0);
419         Cash            c2 = PG_GETARG_CASH(1);
420
421         PG_RETURN_BOOL(c1 > c2);
422 }
423
424 Datum
425 cash_ge(PG_FUNCTION_ARGS)
426 {
427         Cash            c1 = PG_GETARG_CASH(0);
428         Cash            c2 = PG_GETARG_CASH(1);
429
430         PG_RETURN_BOOL(c1 >= c2);
431 }
432
433 Datum
434 cash_cmp(PG_FUNCTION_ARGS)
435 {
436         Cash            c1 = PG_GETARG_CASH(0);
437         Cash            c2 = PG_GETARG_CASH(1);
438
439         if (c1 > c2)
440                 PG_RETURN_INT32(1);
441         else if (c1 == c2)
442                 PG_RETURN_INT32(0);
443         else
444                 PG_RETURN_INT32(-1);
445 }
446
447
448 /* cash_pl()
449  * Add two cash values.
450  */
451 Datum
452 cash_pl(PG_FUNCTION_ARGS)
453 {
454         Cash            c1 = PG_GETARG_CASH(0);
455         Cash            c2 = PG_GETARG_CASH(1);
456         Cash            result;
457
458         result = c1 + c2;
459
460         PG_RETURN_CASH(result);
461 }
462
463
464 /* cash_mi()
465  * Subtract two cash values.
466  */
467 Datum
468 cash_mi(PG_FUNCTION_ARGS)
469 {
470         Cash            c1 = PG_GETARG_CASH(0);
471         Cash            c2 = PG_GETARG_CASH(1);
472         Cash            result;
473
474         result = c1 - c2;
475
476         PG_RETURN_CASH(result);
477 }
478
479
480 /* cash_div_cash()
481  * Divide cash by cash, returning float8.
482  */
483 Datum
484 cash_div_cash(PG_FUNCTION_ARGS)
485 {
486         Cash            dividend = PG_GETARG_CASH(0);
487         Cash            divisor = PG_GETARG_CASH(1);
488         float8          quotient;
489
490         if (divisor == 0)
491                 ereport(ERROR,
492                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
493                                  errmsg("division by zero")));
494
495         quotient = (float8) dividend / (float8) divisor;
496         PG_RETURN_FLOAT8(quotient);
497 }
498
499
500 /* cash_mul_flt8()
501  * Multiply cash by float8.
502  */
503 Datum
504 cash_mul_flt8(PG_FUNCTION_ARGS)
505 {
506         Cash            c = PG_GETARG_CASH(0);
507         float8          f = PG_GETARG_FLOAT8(1);
508         Cash            result;
509
510         result = c * f;
511         PG_RETURN_CASH(result);
512 }
513
514
515 /* flt8_mul_cash()
516  * Multiply float8 by cash.
517  */
518 Datum
519 flt8_mul_cash(PG_FUNCTION_ARGS)
520 {
521         float8          f = PG_GETARG_FLOAT8(0);
522         Cash            c = PG_GETARG_CASH(1);
523         Cash            result;
524
525         result = f * c;
526         PG_RETURN_CASH(result);
527 }
528
529
530 /* cash_div_flt8()
531  * Divide cash by float8.
532  */
533 Datum
534 cash_div_flt8(PG_FUNCTION_ARGS)
535 {
536         Cash            c = PG_GETARG_CASH(0);
537         float8          f = PG_GETARG_FLOAT8(1);
538         Cash            result;
539
540         if (f == 0.0)
541                 ereport(ERROR,
542                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
543                                  errmsg("division by zero")));
544
545         result = rint(c / f);
546         PG_RETURN_CASH(result);
547 }
548
549
550 /* cash_mul_flt4()
551  * Multiply cash by float4.
552  */
553 Datum
554 cash_mul_flt4(PG_FUNCTION_ARGS)
555 {
556         Cash            c = PG_GETARG_CASH(0);
557         float4          f = PG_GETARG_FLOAT4(1);
558         Cash            result;
559
560         result = c * f;
561         PG_RETURN_CASH(result);
562 }
563
564
565 /* flt4_mul_cash()
566  * Multiply float4 by cash.
567  */
568 Datum
569 flt4_mul_cash(PG_FUNCTION_ARGS)
570 {
571         float4          f = PG_GETARG_FLOAT4(0);
572         Cash            c = PG_GETARG_CASH(1);
573         Cash            result;
574
575         result = f * c;
576         PG_RETURN_CASH(result);
577 }
578
579
580 /* cash_div_flt4()
581  * Divide cash by float4.
582  *
583  */
584 Datum
585 cash_div_flt4(PG_FUNCTION_ARGS)
586 {
587         Cash            c = PG_GETARG_CASH(0);
588         float4          f = PG_GETARG_FLOAT4(1);
589         Cash            result;
590
591         if (f == 0.0)
592                 ereport(ERROR,
593                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
594                                  errmsg("division by zero")));
595
596         result = rint(c / f);
597         PG_RETURN_CASH(result);
598 }
599
600
601 /* cash_mul_int8()
602  * Multiply cash by int8.
603  */
604 Datum
605 cash_mul_int8(PG_FUNCTION_ARGS)
606 {
607         Cash            c = PG_GETARG_CASH(0);
608         int64           i = PG_GETARG_INT64(1);
609         Cash            result;
610
611         result = c * i;
612         PG_RETURN_CASH(result);
613 }
614
615
616 /* int8_mul_cash()
617  * Multiply int8 by cash.
618  */
619 Datum
620 int8_mul_cash(PG_FUNCTION_ARGS)
621 {
622         int64           i = PG_GETARG_INT64(0);
623         Cash            c = PG_GETARG_CASH(1);
624         Cash            result;
625
626         result = i * c;
627         PG_RETURN_CASH(result);
628 }
629
630 /* cash_div_int8()
631  * Divide cash by 8-byte integer.
632  */
633 Datum
634 cash_div_int8(PG_FUNCTION_ARGS)
635 {
636         Cash            c = PG_GETARG_CASH(0);
637         int64           i = PG_GETARG_INT64(1);
638         Cash            result;
639
640         if (i == 0)
641                 ereport(ERROR,
642                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
643                                  errmsg("division by zero")));
644
645         result = rint(c / i);
646
647         PG_RETURN_CASH(result);
648 }
649
650
651 /* cash_mul_int4()
652  * Multiply cash by int4.
653  */
654 Datum
655 cash_mul_int4(PG_FUNCTION_ARGS)
656 {
657         Cash            c = PG_GETARG_CASH(0);
658         int32           i = PG_GETARG_INT32(1);
659         Cash            result;
660
661         result = c * i;
662         PG_RETURN_CASH(result);
663 }
664
665
666 /* int4_mul_cash()
667  * Multiply int4 by cash.
668  */
669 Datum
670 int4_mul_cash(PG_FUNCTION_ARGS)
671 {
672         int32           i = PG_GETARG_INT32(0);
673         Cash            c = PG_GETARG_CASH(1);
674         Cash            result;
675
676         result = i * c;
677         PG_RETURN_CASH(result);
678 }
679
680
681 /* cash_div_int4()
682  * Divide cash by 4-byte integer.
683  *
684  */
685 Datum
686 cash_div_int4(PG_FUNCTION_ARGS)
687 {
688         Cash            c = PG_GETARG_CASH(0);
689         int32           i = PG_GETARG_INT32(1);
690         Cash            result;
691
692         if (i == 0)
693                 ereport(ERROR,
694                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
695                                  errmsg("division by zero")));
696
697         result = rint(c / i);
698
699         PG_RETURN_CASH(result);
700 }
701
702
703 /* cash_mul_int2()
704  * Multiply cash by int2.
705  */
706 Datum
707 cash_mul_int2(PG_FUNCTION_ARGS)
708 {
709         Cash            c = PG_GETARG_CASH(0);
710         int16           s = PG_GETARG_INT16(1);
711         Cash            result;
712
713         result = c * s;
714         PG_RETURN_CASH(result);
715 }
716
717 /* int2_mul_cash()
718  * Multiply int2 by cash.
719  */
720 Datum
721 int2_mul_cash(PG_FUNCTION_ARGS)
722 {
723         int16           s = PG_GETARG_INT16(0);
724         Cash            c = PG_GETARG_CASH(1);
725         Cash            result;
726
727         result = s * c;
728         PG_RETURN_CASH(result);
729 }
730
731 /* cash_div_int2()
732  * Divide cash by int2.
733  *
734  */
735 Datum
736 cash_div_int2(PG_FUNCTION_ARGS)
737 {
738         Cash            c = PG_GETARG_CASH(0);
739         int16           s = PG_GETARG_INT16(1);
740         Cash            result;
741
742         if (s == 0)
743                 ereport(ERROR,
744                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
745                                  errmsg("division by zero")));
746
747         result = rint(c / s);
748         PG_RETURN_CASH(result);
749 }
750
751 /* cashlarger()
752  * Return larger of two cash values.
753  */
754 Datum
755 cashlarger(PG_FUNCTION_ARGS)
756 {
757         Cash            c1 = PG_GETARG_CASH(0);
758         Cash            c2 = PG_GETARG_CASH(1);
759         Cash            result;
760
761         result = (c1 > c2) ? c1 : c2;
762
763         PG_RETURN_CASH(result);
764 }
765
766 /* cashsmaller()
767  * Return smaller of two cash values.
768  */
769 Datum
770 cashsmaller(PG_FUNCTION_ARGS)
771 {
772         Cash            c1 = PG_GETARG_CASH(0);
773         Cash            c2 = PG_GETARG_CASH(1);
774         Cash            result;
775
776         result = (c1 < c2) ? c1 : c2;
777
778         PG_RETURN_CASH(result);
779 }
780
781 /* cash_words()
782  * This converts a int4 as well but to a representation using words
783  * Obviously way North American centric - sorry
784  */
785 Datum
786 cash_words(PG_FUNCTION_ARGS)
787 {
788         Cash            value = PG_GETARG_CASH(0);
789         uint64          val;
790         char            buf[256];
791         char       *p = buf;
792         Cash            m0;
793         Cash            m1;
794         Cash            m2;
795         Cash            m3;
796         Cash            m4;
797         Cash            m5;
798         Cash            m6;
799
800         /* work with positive numbers */
801         if (value < 0)
802         {
803                 value = -value;
804                 strcpy(buf, "minus ");
805                 p += 6;
806         }
807         else
808                 buf[0] = '\0';
809
810         /* Now treat as unsigned, to avoid trouble at INT_MIN */
811         val = (uint64) value;
812
813         m0 = val % INT64CONST(100); /* cents */
814         m1 = (val / INT64CONST(100)) % 1000;            /* hundreds */
815         m2 = (val / INT64CONST(100000)) % 1000;         /* thousands */
816         m3 = (val / INT64CONST(100000000)) % 1000;      /* millions */
817         m4 = (val / INT64CONST(100000000000)) % 1000;           /* billions */
818         m5 = (val / INT64CONST(100000000000000)) % 1000;        /* trillions */
819         m6 = (val / INT64CONST(100000000000000000)) % 1000; /* quadrillions */
820
821         if (m6)
822         {
823                 strcat(buf, num_word(m6));
824                 strcat(buf, " quadrillion ");
825         }
826
827         if (m5)
828         {
829                 strcat(buf, num_word(m5));
830                 strcat(buf, " trillion ");
831         }
832
833         if (m4)
834         {
835                 strcat(buf, num_word(m4));
836                 strcat(buf, " billion ");
837         }
838
839         if (m3)
840         {
841                 strcat(buf, num_word(m3));
842                 strcat(buf, " million ");
843         }
844
845         if (m2)
846         {
847                 strcat(buf, num_word(m2));
848                 strcat(buf, " thousand ");
849         }
850
851         if (m1)
852                 strcat(buf, num_word(m1));
853
854         if (!*p)
855                 strcat(buf, "zero");
856
857         strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
858         strcat(buf, num_word(m0));
859         strcat(buf, m0 == 1 ? " cent" : " cents");
860
861         /* capitalize output */
862         buf[0] = pg_toupper((unsigned char) buf[0]);
863
864         /* return as text datum */
865         PG_RETURN_TEXT_P(cstring_to_text(buf));
866 }
867
868
869 /* cash_numeric()
870  * Convert cash to numeric.
871  */
872 Datum
873 cash_numeric(PG_FUNCTION_ARGS)
874 {
875         Cash            money = PG_GETARG_CASH(0);
876         Numeric         result;
877         int                     fpoint;
878         int64           scale;
879         int                     i;
880         Datum           amount;
881         Datum           numeric_scale;
882         Datum           quotient;
883         struct lconv *lconvert = PGLC_localeconv();
884
885         /* see comments about frac_digits in cash_in() */
886         fpoint = lconvert->frac_digits;
887         if (fpoint < 0 || fpoint > 10)
888                 fpoint = 2;
889
890         /* compute required scale factor */
891         scale = 1;
892         for (i = 0; i < fpoint; i++)
893                 scale *= 10;
894
895         /* form the result as money / scale */
896         amount = DirectFunctionCall1(int8_numeric, Int64GetDatum(money));
897         numeric_scale = DirectFunctionCall1(int8_numeric, Int64GetDatum(scale));
898         quotient = DirectFunctionCall2(numeric_div, amount, numeric_scale);
899
900         /* forcibly round to exactly the intended number of digits */
901         result = DatumGetNumeric(DirectFunctionCall2(numeric_round,
902                                                                                                  quotient,
903                                                                                                  Int32GetDatum(fpoint)));
904
905         PG_RETURN_NUMERIC(result);
906 }
907
908 /* numeric_cash()
909  * Convert numeric to cash.
910  */
911 Datum
912 numeric_cash(PG_FUNCTION_ARGS)
913 {
914         Datum           amount = PG_GETARG_DATUM(0);
915         Cash            result;
916         int                     fpoint;
917         int64           scale;
918         int                     i;
919         Datum           numeric_scale;
920         struct lconv *lconvert = PGLC_localeconv();
921
922         /* see comments about frac_digits in cash_in() */
923         fpoint = lconvert->frac_digits;
924         if (fpoint < 0 || fpoint > 10)
925                 fpoint = 2;
926
927         /* compute required scale factor */
928         scale = 1;
929         for (i = 0; i < fpoint; i++)
930                 scale *= 10;
931
932         /* multiply the input amount by scale factor */
933         numeric_scale = DirectFunctionCall1(int8_numeric, Int64GetDatum(scale));
934         amount = DirectFunctionCall2(numeric_mul, amount, numeric_scale);
935
936         /* note that numeric_int8 will round to nearest integer for us */
937         result = DatumGetInt64(DirectFunctionCall1(numeric_int8, amount));
938
939         PG_RETURN_CASH(result);
940 }