]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/cash.c
Clarify how MONEY trims off trailing thousands separator.
[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.77 2007/11/24 16:18:48 momjian 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/cash.h"
28 #include "utils/pg_locale.h"
29
30 #define CASH_BUFSZ              36
31
32 #define TERMINATOR              (CASH_BUFSZ - 1)
33 #define LAST_PAREN              (TERMINATOR - 1)
34 #define LAST_DIGIT              (LAST_PAREN - 1)
35
36 /*
37  * Cash is a pass-by-ref SQL type, so we must pass and return pointers.
38  * These macros and support routine hide the pass-by-refness.
39  */
40 #define PG_GETARG_CASH(n)  (* ((Cash *) PG_GETARG_POINTER(n)))
41 #define PG_RETURN_CASH(x)  return CashGetDatum(x)
42
43
44
45 /*************************************************************************
46  * Private routines
47  ************************************************************************/
48
49 static const char *
50 num_word(Cash value)
51 {
52         static char buf[128];
53         static const char *small[] = {
54                 "zero", "one", "two", "three", "four", "five", "six", "seven",
55                 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
56                 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
57                 "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
58         };
59         const char **big = small + 18;
60         int                     tu = value % 100;
61
62         /* deal with the simple cases first */
63         if (value <= 20)
64                 return small[value];
65
66         /* is it an even multiple of 100? */
67         if (!tu)
68         {
69                 sprintf(buf, "%s hundred", small[value / 100]);
70                 return buf;
71         }
72
73         /* more than 99? */
74         if (value > 99)
75         {
76                 /* is it an even multiple of 10 other than 10? */
77                 if (value % 10 == 0 && tu > 10)
78                         sprintf(buf, "%s hundred %s",
79                                         small[value / 100], big[tu / 10]);
80                 else if (tu < 20)
81                         sprintf(buf, "%s hundred and %s",
82                                         small[value / 100], small[tu]);
83                 else
84                         sprintf(buf, "%s hundred %s %s",
85                                         small[value / 100], big[tu / 10], small[tu % 10]);
86         }
87         else
88         {
89                 /* is it an even multiple of 10 other than 10? */
90                 if (value % 10 == 0 && tu > 10)
91                         sprintf(buf, "%s", big[tu / 10]);
92                 else if (tu < 20)
93                         sprintf(buf, "%s", small[tu]);
94                 else
95                         sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
96         }
97
98         return buf;
99 }       /* num_word() */
100
101 static Datum
102 CashGetDatum(Cash value)
103 {
104         Cash       *result = (Cash *) palloc(sizeof(Cash));
105
106         *result = value;
107         return PointerGetDatum(result);
108 }
109
110
111 /* cash_in()
112  * Convert a string to a cash data type.
113  * Format is [$]###[,]###[.##]
114  * Examples: 123.45 $123.45 $123,456.78
115  *
116  */
117 Datum
118 cash_in(PG_FUNCTION_ARGS)
119 {
120         char       *str = PG_GETARG_CSTRING(0);
121         Cash            result;
122         Cash            value = 0;
123         Cash            dec = 0;
124         Cash            sgn = 1;
125         int                     seen_dot = 0;
126         const char *s = str;
127         int                     fpoint;
128         char            dsymbol,
129                                 ssymbol,
130                                 psymbol;
131         const char *nsymbol,
132                            *csymbol;
133
134         struct lconv *lconvert = PGLC_localeconv();
135
136         /*
137          * frac_digits will be CHAR_MAX in some locales, notably C.  However, just
138          * testing for == CHAR_MAX is risky, because of compilers like gcc that
139          * "helpfully" let you alter the platform-standard definition of whether
140          * char is signed or not.  If we are so unfortunate as to get compiled
141          * with a nonstandard -fsigned-char or -funsigned-char switch, then our
142          * idea of CHAR_MAX will not agree with libc's. The safest course is not
143          * to test for CHAR_MAX at all, but to impose a range check for plausible
144          * frac_digits values.
145          */
146         fpoint = lconvert->frac_digits;
147         if (fpoint < 0 || fpoint > 10)
148                 fpoint = 2;                             /* best guess in this case, I think */
149
150         dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
151         if (*lconvert->mon_thousands_sep != '\0')
152                 ssymbol = *lconvert->mon_thousands_sep;
153         else
154                 /* ssymbol should not equal dsymbol */
155                 ssymbol = (dsymbol != ',') ? ',' : '.';
156         csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
157         psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
158         nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
159
160 #ifdef CASHDEBUG
161         printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
162                    fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
163 #endif
164
165         /* we need to add all sorts of checking here.  For now just */
166         /* strip all leading whitespace and any leading currency symbol */
167         while (isspace((unsigned char) *s))
168                 s++;
169         if (strncmp(s, csymbol, strlen(csymbol)) == 0)
170                 s += strlen(csymbol);
171
172 #ifdef CASHDEBUG
173         printf("cashin- string is '%s'\n", s);
174 #endif
175
176         /* a leading minus or paren signifies a negative number */
177         /* again, better heuristics needed */
178         /* XXX - doesn't properly check for balanced parens - djmc */
179         if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
180         {
181                 sgn = -1;
182                 s += strlen(nsymbol);
183 #ifdef CASHDEBUG
184                 printf("cashin- negative symbol; string is '%s'\n", s);
185 #endif
186         }
187         else if (*s == '(')
188         {
189                 sgn = -1;
190                 s++;
191         }
192         else if (*s == psymbol)
193                 s++;
194
195 #ifdef CASHDEBUG
196         printf("cashin- string is '%s'\n", s);
197 #endif
198
199         while (isspace((unsigned char) *s))
200                 s++;
201         if (strncmp(s, csymbol, strlen(csymbol)) == 0)
202                 s += strlen(csymbol);
203
204 #ifdef CASHDEBUG
205         printf("cashin- string is '%s'\n", s);
206 #endif
207
208         for (;; s++)
209         {
210                 /* we look for digits as int8 as we have less */
211                 /* than the required number of decimal places */
212                 if (isdigit((unsigned char) *s) && dec < fpoint)
213                 {
214                         value = (value * 10) + *s - '0';
215
216                         if (seen_dot)
217                                 dec++;
218
219                 }
220                 /* decimal point? then start counting fractions... */
221                 else if (*s == dsymbol && !seen_dot)
222                 {
223                         seen_dot = 1;
224
225                 }
226                 /* not "thousands" separator? */
227                 else if (*s != ssymbol)
228                 {
229                         /* round off */
230                         if (isdigit((unsigned char) *s) && *s >= '5')
231                                 value++;
232
233                         /* adjust for less than required decimal places */
234                         for (; dec < fpoint; dec++)
235                                 value *= 10;
236
237                         break;
238                 }
239         }
240
241         /* should only be trailing digits followed by whitespace or right paren */
242         while (isdigit((unsigned char) *s))
243                 s++;
244         while (isspace((unsigned char) *s) || *s == ')')
245                 s++;
246
247         if (*s != '\0')
248                 ereport(ERROR,
249                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
250                                  errmsg("invalid input syntax for type money: \"%s\"", str)));
251
252         result = value * sgn;
253
254 #ifdef CASHDEBUG
255         printf("cashin- result is %d\n", result);
256 #endif
257
258         PG_RETURN_CASH(result);
259 }
260
261
262 /* cash_out()
263  * Function to convert cash to a dollars and cents representation.
264  * XXX HACK This code appears to assume US conventions for
265  *      positive-valued amounts. - tgl 97/04/14
266  */
267 Datum
268 cash_out(PG_FUNCTION_ARGS)
269 {
270         Cash            value = PG_GETARG_CASH(0);
271         char       *result;
272         char            buf[CASH_BUFSZ];
273         int                     minus = 0;
274         int                     count = LAST_DIGIT;
275         int                     point_pos;
276         int                     ssymbol_position = 0;
277         int                     points,
278                                 mon_group;
279         char            ssymbol;
280         const char *csymbol,
281                            *nsymbol;
282         char            dsymbol;
283         char            convention;
284
285         struct lconv *lconvert = PGLC_localeconv();
286
287         /* see comments about frac_digits in cash_in() */
288         points = lconvert->frac_digits;
289         if (points < 0 || points > 10)
290                 points = 2;                             /* best guess in this case, I think */
291
292         /*
293          * As with frac_digits, must apply a range check to mon_grouping to avoid
294          * being fooled by variant CHAR_MAX values.
295          */
296         mon_group = *lconvert->mon_grouping;
297         if (mon_group <= 0 || mon_group > 6)
298                 mon_group = 3;
299
300         convention = lconvert->n_sign_posn;
301         dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
302         if (*lconvert->mon_thousands_sep != '\0')
303                 ssymbol = *lconvert->mon_thousands_sep;
304         else
305                 /* ssymbol should not equal dsymbol */
306                 ssymbol = (dsymbol != ',') ? ',' : '.';
307         csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
308         nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
309
310         point_pos = LAST_DIGIT - points;
311
312         point_pos -= (points - 1) / mon_group;
313         ssymbol_position = point_pos % (mon_group + 1);
314
315         /* we work with positive amounts and add the minus sign at the end */
316         if (value < 0)
317         {
318                 minus = 1;
319                 value = -value;
320         }
321
322         /* allow for trailing negative strings */
323         MemSet(buf, ' ', CASH_BUFSZ);
324         buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
325
326         while (value || count > (point_pos - 2))
327         {
328                 if (points && count == point_pos)
329                         buf[count--] = dsymbol;
330                 else if (ssymbol && count % (mon_group + 1) == ssymbol_position)
331                         buf[count--] = ssymbol;
332
333                 buf[count--] = ((uint64) value % 10) + '0';
334                 value = ((uint64) value) / 10;
335         }
336
337         strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
338         count -= strlen(csymbol) - 1;
339
340         /*
341          *      If points == 0 and the number of digits % mon_group == 0,
342          *      the code above adds a trailing ssymbol on the far right,
343          *      so remove it.
344          */
345         if (buf[LAST_DIGIT] == ssymbol)
346                 buf[LAST_DIGIT] = '\0';
347
348         /* see if we need to signify negative amount */
349         if (minus)
350         {
351                 result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
352
353                 /* Position code of 0 means use parens */
354                 if (convention == 0)
355                         sprintf(result, "(%s)", buf + count);
356                 else if (convention == 2)
357                         sprintf(result, "%s%s", buf + count, nsymbol);
358                 else
359                         sprintf(result, "%s%s", nsymbol, buf + count);
360         }
361         else
362         {
363                 result = palloc(CASH_BUFSZ + 2 - count);
364                 strcpy(result, buf + count);
365         }
366
367         PG_RETURN_CSTRING(result);
368 }
369
370 /*
371  *              cash_recv                       - converts external binary format to cash
372  */
373 Datum
374 cash_recv(PG_FUNCTION_ARGS)
375 {
376         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
377
378         PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
379 }
380
381 /*
382  *              cash_send                       - converts cash to binary format
383  */
384 Datum
385 cash_send(PG_FUNCTION_ARGS)
386 {
387         Cash            arg1 = PG_GETARG_CASH(0);
388         StringInfoData buf;
389
390         pq_begintypsend(&buf);
391         pq_sendint64(&buf, arg1);
392         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
393 }
394
395 /*
396  * Comparison functions
397  */
398
399 Datum
400 cash_eq(PG_FUNCTION_ARGS)
401 {
402         Cash            c1 = PG_GETARG_CASH(0);
403         Cash            c2 = PG_GETARG_CASH(1);
404
405         PG_RETURN_BOOL(c1 == c2);
406 }
407
408 Datum
409 cash_ne(PG_FUNCTION_ARGS)
410 {
411         Cash            c1 = PG_GETARG_CASH(0);
412         Cash            c2 = PG_GETARG_CASH(1);
413
414         PG_RETURN_BOOL(c1 != c2);
415 }
416
417 Datum
418 cash_lt(PG_FUNCTION_ARGS)
419 {
420         Cash            c1 = PG_GETARG_CASH(0);
421         Cash            c2 = PG_GETARG_CASH(1);
422
423         PG_RETURN_BOOL(c1 < c2);
424 }
425
426 Datum
427 cash_le(PG_FUNCTION_ARGS)
428 {
429         Cash            c1 = PG_GETARG_CASH(0);
430         Cash            c2 = PG_GETARG_CASH(1);
431
432         PG_RETURN_BOOL(c1 <= c2);
433 }
434
435 Datum
436 cash_gt(PG_FUNCTION_ARGS)
437 {
438         Cash            c1 = PG_GETARG_CASH(0);
439         Cash            c2 = PG_GETARG_CASH(1);
440
441         PG_RETURN_BOOL(c1 > c2);
442 }
443
444 Datum
445 cash_ge(PG_FUNCTION_ARGS)
446 {
447         Cash            c1 = PG_GETARG_CASH(0);
448         Cash            c2 = PG_GETARG_CASH(1);
449
450         PG_RETURN_BOOL(c1 >= c2);
451 }
452
453 Datum
454 cash_cmp(PG_FUNCTION_ARGS)
455 {
456         Cash            c1 = PG_GETARG_CASH(0);
457         Cash            c2 = PG_GETARG_CASH(1);
458
459         if (c1 > c2)
460                 PG_RETURN_INT32(1);
461         else if (c1 == c2)
462                 PG_RETURN_INT32(0);
463         else
464                 PG_RETURN_INT32(-1);
465 }
466
467
468 /* cash_pl()
469  * Add two cash values.
470  */
471 Datum
472 cash_pl(PG_FUNCTION_ARGS)
473 {
474         Cash            c1 = PG_GETARG_CASH(0);
475         Cash            c2 = PG_GETARG_CASH(1);
476         Cash            result;
477
478         result = c1 + c2;
479
480         PG_RETURN_CASH(result);
481 }
482
483
484 /* cash_mi()
485  * Subtract two cash values.
486  */
487 Datum
488 cash_mi(PG_FUNCTION_ARGS)
489 {
490         Cash            c1 = PG_GETARG_CASH(0);
491         Cash            c2 = PG_GETARG_CASH(1);
492         Cash            result;
493
494         result = c1 - c2;
495
496         PG_RETURN_CASH(result);
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         text       *result;
800
801         /* work with positive numbers */
802         if (value < 0)
803         {
804                 value = -value;
805                 strcpy(buf, "minus ");
806                 p += 6;
807         }
808         else
809                 buf[0] = '\0';
810
811         /* Now treat as unsigned, to avoid trouble at INT_MIN */
812         val = (uint64) value;
813
814         m0 = val % 100ll;                       /* cents */
815         m1 = (val / 100ll) % 1000;      /* hundreds */
816         m2 = (val / 100000ll) % 1000;           /* thousands */
817         m3 = val / 100000000ll % 1000;          /* millions */
818         m4 = val / 100000000000ll % 1000;       /* billions */
819         m5 = val / 100000000000000ll % 1000;            /* trillions */
820         m6 = val / 100000000000000000ll % 1000;         /* quadrillions */
821
822         if (m6)
823         {
824                 strcat(buf, num_word(m6));
825                 strcat(buf, " quadrillion ");
826         }
827
828         if (m5)
829         {
830                 strcat(buf, num_word(m5));
831                 strcat(buf, " trillion ");
832         }
833
834         if (m4)
835         {
836                 strcat(buf, num_word(m4));
837                 strcat(buf, " billion ");
838         }
839
840         if (m3)
841         {
842                 strcat(buf, num_word(m3));
843                 strcat(buf, " million ");
844         }
845
846         if (m2)
847         {
848                 strcat(buf, num_word(m2));
849                 strcat(buf, " thousand ");
850         }
851
852         if (m1)
853                 strcat(buf, num_word(m1));
854
855         if (!*p)
856                 strcat(buf, "zero");
857
858         strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
859         strcat(buf, num_word(m0));
860         strcat(buf, m0 == 1 ? " cent" : " cents");
861
862         /* capitalize output */
863         buf[0] = pg_toupper((unsigned char) buf[0]);
864
865         /* make a text type for output */
866         result = (text *) palloc(strlen(buf) + VARHDRSZ);
867         SET_VARSIZE(result, strlen(buf) + VARHDRSZ);
868         memcpy(VARDATA(result), buf, strlen(buf));
869
870         PG_RETURN_TEXT_P(result);
871 }