]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/cash.c
Fix cash_in() to behave properly in locales where frac_digits is zero,
[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.81 2009/06/10 16:31:32 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/pg_locale.h"
30
31 #define CASH_BUFSZ              36
32
33 #define TERMINATOR              (CASH_BUFSZ - 1)
34 #define LAST_PAREN              (TERMINATOR - 1)
35 #define LAST_DIGIT              (LAST_PAREN - 1)
36
37
38 /*************************************************************************
39  * Private routines
40  ************************************************************************/
41
42 static const char *
43 num_word(Cash value)
44 {
45         static char buf[128];
46         static const char *small[] = {
47                 "zero", "one", "two", "three", "four", "five", "six", "seven",
48                 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
49                 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
50                 "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
51         };
52         const char **big = small + 18;
53         int                     tu = value % 100;
54
55         /* deal with the simple cases first */
56         if (value <= 20)
57                 return small[value];
58
59         /* is it an even multiple of 100? */
60         if (!tu)
61         {
62                 sprintf(buf, "%s hundred", small[value / 100]);
63                 return buf;
64         }
65
66         /* more than 99? */
67         if (value > 99)
68         {
69                 /* is it an even multiple of 10 other than 10? */
70                 if (value % 10 == 0 && tu > 10)
71                         sprintf(buf, "%s hundred %s",
72                                         small[value / 100], big[tu / 10]);
73                 else if (tu < 20)
74                         sprintf(buf, "%s hundred and %s",
75                                         small[value / 100], small[tu]);
76                 else
77                         sprintf(buf, "%s hundred %s %s",
78                                         small[value / 100], big[tu / 10], small[tu % 10]);
79         }
80         else
81         {
82                 /* is it an even multiple of 10 other than 10? */
83                 if (value % 10 == 0 && tu > 10)
84                         sprintf(buf, "%s", big[tu / 10]);
85                 else if (tu < 20)
86                         sprintf(buf, "%s", small[tu]);
87                 else
88                         sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
89         }
90
91         return buf;
92 }       /* num_word() */
93
94
95 /* cash_in()
96  * Convert a string to a cash data type.
97  * Format is [$]###[,]###[.##]
98  * Examples: 123.45 $123.45 $123,456.78
99  *
100  */
101 Datum
102 cash_in(PG_FUNCTION_ARGS)
103 {
104         char       *str = PG_GETARG_CSTRING(0);
105         Cash            result;
106         Cash            value = 0;
107         Cash            dec = 0;
108         Cash            sgn = 1;
109         int                     seen_dot = 0;
110         const char *s = str;
111         int                     fpoint;
112         char            dsymbol,
113                                 ssymbol,
114                                 psymbol;
115         const char *nsymbol,
116                            *csymbol;
117
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
267         struct lconv *lconvert = PGLC_localeconv();
268
269         /* see comments about frac_digits in cash_in() */
270         points = lconvert->frac_digits;
271         if (points < 0 || points > 10)
272                 points = 2;                             /* best guess in this case, I think */
273
274         /*
275          * As with frac_digits, must apply a range check to mon_grouping to avoid
276          * being fooled by variant CHAR_MAX values.
277          */
278         mon_group = *lconvert->mon_grouping;
279         if (mon_group <= 0 || mon_group > 6)
280                 mon_group = 3;
281
282         convention = lconvert->n_sign_posn;
283         dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
284         if (*lconvert->mon_thousands_sep != '\0')
285                 ssymbol = *lconvert->mon_thousands_sep;
286         else
287                 /* ssymbol should not equal dsymbol */
288                 ssymbol = (dsymbol != ',') ? ',' : '.';
289         csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
290         nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
291
292         point_pos = LAST_DIGIT - points;
293
294         point_pos -= (points - 1) / mon_group;
295         ssymbol_position = point_pos % (mon_group + 1);
296
297         /* we work with positive amounts and add the minus sign at the end */
298         if (value < 0)
299         {
300                 minus = 1;
301                 value = -value;
302         }
303
304         /* allow for trailing negative strings */
305         MemSet(buf, ' ', CASH_BUFSZ);
306         buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
307
308         while (value || count > (point_pos - 2))
309         {
310                 if (points && count == point_pos)
311                         buf[count--] = dsymbol;
312                 else if (ssymbol && count % (mon_group + 1) == ssymbol_position)
313                         buf[count--] = ssymbol;
314
315                 buf[count--] = ((uint64) value % 10) + '0';
316                 value = ((uint64) value) / 10;
317         }
318
319         strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
320         count -= strlen(csymbol) - 1;
321
322         /*
323          *      If points == 0 and the number of digits % mon_group == 0,
324          *      the code above adds a trailing ssymbol on the far right,
325          *      so remove it.
326          */
327         if (buf[LAST_DIGIT] == ssymbol)
328                 buf[LAST_DIGIT] = '\0';
329
330         /* see if we need to signify negative amount */
331         if (minus)
332         {
333                 result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
334
335                 /* Position code of 0 means use parens */
336                 if (convention == 0)
337                         sprintf(result, "(%s)", buf + count);
338                 else if (convention == 2)
339                         sprintf(result, "%s%s", buf + count, nsymbol);
340                 else
341                         sprintf(result, "%s%s", nsymbol, buf + count);
342         }
343         else
344         {
345                 result = palloc(CASH_BUFSZ + 2 - count);
346                 strcpy(result, buf + count);
347         }
348
349         PG_RETURN_CSTRING(result);
350 }
351
352 /*
353  *              cash_recv                       - converts external binary format to cash
354  */
355 Datum
356 cash_recv(PG_FUNCTION_ARGS)
357 {
358         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
359
360         PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
361 }
362
363 /*
364  *              cash_send                       - converts cash to binary format
365  */
366 Datum
367 cash_send(PG_FUNCTION_ARGS)
368 {
369         Cash            arg1 = PG_GETARG_CASH(0);
370         StringInfoData buf;
371
372         pq_begintypsend(&buf);
373         pq_sendint64(&buf, arg1);
374         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
375 }
376
377 /*
378  * Comparison functions
379  */
380
381 Datum
382 cash_eq(PG_FUNCTION_ARGS)
383 {
384         Cash            c1 = PG_GETARG_CASH(0);
385         Cash            c2 = PG_GETARG_CASH(1);
386
387         PG_RETURN_BOOL(c1 == c2);
388 }
389
390 Datum
391 cash_ne(PG_FUNCTION_ARGS)
392 {
393         Cash            c1 = PG_GETARG_CASH(0);
394         Cash            c2 = PG_GETARG_CASH(1);
395
396         PG_RETURN_BOOL(c1 != c2);
397 }
398
399 Datum
400 cash_lt(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_le(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_gt(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_ge(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_cmp(PG_FUNCTION_ARGS)
437 {
438         Cash            c1 = PG_GETARG_CASH(0);
439         Cash            c2 = PG_GETARG_CASH(1);
440
441         if (c1 > c2)
442                 PG_RETURN_INT32(1);
443         else if (c1 == c2)
444                 PG_RETURN_INT32(0);
445         else
446                 PG_RETURN_INT32(-1);
447 }
448
449
450 /* cash_pl()
451  * Add two cash values.
452  */
453 Datum
454 cash_pl(PG_FUNCTION_ARGS)
455 {
456         Cash            c1 = PG_GETARG_CASH(0);
457         Cash            c2 = PG_GETARG_CASH(1);
458         Cash            result;
459
460         result = c1 + c2;
461
462         PG_RETURN_CASH(result);
463 }
464
465
466 /* cash_mi()
467  * Subtract two cash values.
468  */
469 Datum
470 cash_mi(PG_FUNCTION_ARGS)
471 {
472         Cash            c1 = PG_GETARG_CASH(0);
473         Cash            c2 = PG_GETARG_CASH(1);
474         Cash            result;
475
476         result = c1 - c2;
477
478         PG_RETURN_CASH(result);
479 }
480
481
482 /* cash_mul_flt8()
483  * Multiply cash by float8.
484  */
485 Datum
486 cash_mul_flt8(PG_FUNCTION_ARGS)
487 {
488         Cash            c = PG_GETARG_CASH(0);
489         float8          f = PG_GETARG_FLOAT8(1);
490         Cash            result;
491
492         result = c * f;
493         PG_RETURN_CASH(result);
494 }
495
496
497 /* flt8_mul_cash()
498  * Multiply float8 by cash.
499  */
500 Datum
501 flt8_mul_cash(PG_FUNCTION_ARGS)
502 {
503         float8          f = PG_GETARG_FLOAT8(0);
504         Cash            c = PG_GETARG_CASH(1);
505         Cash            result;
506
507         result = f * c;
508         PG_RETURN_CASH(result);
509 }
510
511
512 /* cash_div_flt8()
513  * Divide cash by float8.
514  */
515 Datum
516 cash_div_flt8(PG_FUNCTION_ARGS)
517 {
518         Cash            c = PG_GETARG_CASH(0);
519         float8          f = PG_GETARG_FLOAT8(1);
520         Cash            result;
521
522         if (f == 0.0)
523                 ereport(ERROR,
524                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
525                                  errmsg("division by zero")));
526
527         result = rint(c / f);
528         PG_RETURN_CASH(result);
529 }
530
531
532 /* cash_mul_flt4()
533  * Multiply cash by float4.
534  */
535 Datum
536 cash_mul_flt4(PG_FUNCTION_ARGS)
537 {
538         Cash            c = PG_GETARG_CASH(0);
539         float4          f = PG_GETARG_FLOAT4(1);
540         Cash            result;
541
542         result = c * f;
543         PG_RETURN_CASH(result);
544 }
545
546
547 /* flt4_mul_cash()
548  * Multiply float4 by cash.
549  */
550 Datum
551 flt4_mul_cash(PG_FUNCTION_ARGS)
552 {
553         float4          f = PG_GETARG_FLOAT4(0);
554         Cash            c = PG_GETARG_CASH(1);
555         Cash            result;
556
557         result = f * c;
558         PG_RETURN_CASH(result);
559 }
560
561
562 /* cash_div_flt4()
563  * Divide cash by float4.
564  *
565  */
566 Datum
567 cash_div_flt4(PG_FUNCTION_ARGS)
568 {
569         Cash            c = PG_GETARG_CASH(0);
570         float4          f = PG_GETARG_FLOAT4(1);
571         Cash            result;
572
573         if (f == 0.0)
574                 ereport(ERROR,
575                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
576                                  errmsg("division by zero")));
577
578         result = rint(c / f);
579         PG_RETURN_CASH(result);
580 }
581
582
583 /* cash_mul_int8()
584  * Multiply cash by int8.
585  */
586 Datum
587 cash_mul_int8(PG_FUNCTION_ARGS)
588 {
589         Cash            c = PG_GETARG_CASH(0);
590         int64           i = PG_GETARG_INT64(1);
591         Cash            result;
592
593         result = c * i;
594         PG_RETURN_CASH(result);
595 }
596
597
598 /* int8_mul_cash()
599  * Multiply int8 by cash.
600  */
601 Datum
602 int8_mul_cash(PG_FUNCTION_ARGS)
603 {
604         int64           i = PG_GETARG_INT64(0);
605         Cash            c = PG_GETARG_CASH(1);
606         Cash            result;
607
608         result = i * c;
609         PG_RETURN_CASH(result);
610 }
611
612 /* cash_div_int8()
613  * Divide cash by 8-byte integer.
614  */
615 Datum
616 cash_div_int8(PG_FUNCTION_ARGS)
617 {
618         Cash            c = PG_GETARG_CASH(0);
619         int64           i = PG_GETARG_INT64(1);
620         Cash            result;
621
622         if (i == 0)
623                 ereport(ERROR,
624                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
625                                  errmsg("division by zero")));
626
627         result = rint(c / i);
628
629         PG_RETURN_CASH(result);
630 }
631
632
633 /* cash_mul_int4()
634  * Multiply cash by int4.
635  */
636 Datum
637 cash_mul_int4(PG_FUNCTION_ARGS)
638 {
639         Cash            c = PG_GETARG_CASH(0);
640         int32           i = PG_GETARG_INT32(1);
641         Cash            result;
642
643         result = c * i;
644         PG_RETURN_CASH(result);
645 }
646
647
648 /* int4_mul_cash()
649  * Multiply int4 by cash.
650  */
651 Datum
652 int4_mul_cash(PG_FUNCTION_ARGS)
653 {
654         int32           i = PG_GETARG_INT32(0);
655         Cash            c = PG_GETARG_CASH(1);
656         Cash            result;
657
658         result = i * c;
659         PG_RETURN_CASH(result);
660 }
661
662
663 /* cash_div_int4()
664  * Divide cash by 4-byte integer.
665  *
666  */
667 Datum
668 cash_div_int4(PG_FUNCTION_ARGS)
669 {
670         Cash            c = PG_GETARG_CASH(0);
671         int32           i = PG_GETARG_INT32(1);
672         Cash            result;
673
674         if (i == 0)
675                 ereport(ERROR,
676                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
677                                  errmsg("division by zero")));
678
679         result = rint(c / i);
680
681         PG_RETURN_CASH(result);
682 }
683
684
685 /* cash_mul_int2()
686  * Multiply cash by int2.
687  */
688 Datum
689 cash_mul_int2(PG_FUNCTION_ARGS)
690 {
691         Cash            c = PG_GETARG_CASH(0);
692         int16           s = PG_GETARG_INT16(1);
693         Cash            result;
694
695         result = c * s;
696         PG_RETURN_CASH(result);
697 }
698
699 /* int2_mul_cash()
700  * Multiply int2 by cash.
701  */
702 Datum
703 int2_mul_cash(PG_FUNCTION_ARGS)
704 {
705         int16           s = PG_GETARG_INT16(0);
706         Cash            c = PG_GETARG_CASH(1);
707         Cash            result;
708
709         result = s * c;
710         PG_RETURN_CASH(result);
711 }
712
713 /* cash_div_int2()
714  * Divide cash by int2.
715  *
716  */
717 Datum
718 cash_div_int2(PG_FUNCTION_ARGS)
719 {
720         Cash            c = PG_GETARG_CASH(0);
721         int16           s = PG_GETARG_INT16(1);
722         Cash            result;
723
724         if (s == 0)
725                 ereport(ERROR,
726                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
727                                  errmsg("division by zero")));
728
729         result = rint(c / s);
730         PG_RETURN_CASH(result);
731 }
732
733 /* cashlarger()
734  * Return larger of two cash values.
735  */
736 Datum
737 cashlarger(PG_FUNCTION_ARGS)
738 {
739         Cash            c1 = PG_GETARG_CASH(0);
740         Cash            c2 = PG_GETARG_CASH(1);
741         Cash            result;
742
743         result = (c1 > c2) ? c1 : c2;
744
745         PG_RETURN_CASH(result);
746 }
747
748 /* cashsmaller()
749  * Return smaller of two cash values.
750  */
751 Datum
752 cashsmaller(PG_FUNCTION_ARGS)
753 {
754         Cash            c1 = PG_GETARG_CASH(0);
755         Cash            c2 = PG_GETARG_CASH(1);
756         Cash            result;
757
758         result = (c1 < c2) ? c1 : c2;
759
760         PG_RETURN_CASH(result);
761 }
762
763 /* cash_words()
764  * This converts a int4 as well but to a representation using words
765  * Obviously way North American centric - sorry
766  */
767 Datum
768 cash_words(PG_FUNCTION_ARGS)
769 {
770         Cash            value = PG_GETARG_CASH(0);
771         uint64          val;
772         char            buf[256];
773         char       *p = buf;
774         Cash            m0;
775         Cash            m1;
776         Cash            m2;
777         Cash            m3;
778         Cash            m4;
779         Cash            m5;
780         Cash            m6;
781
782         /* work with positive numbers */
783         if (value < 0)
784         {
785                 value = -value;
786                 strcpy(buf, "minus ");
787                 p += 6;
788         }
789         else
790                 buf[0] = '\0';
791
792         /* Now treat as unsigned, to avoid trouble at INT_MIN */
793         val = (uint64) value;
794
795         m0 = val % INT64CONST(100);                                                     /* cents */
796         m1 = (val / INT64CONST(100)) % 1000;                            /* hundreds */
797         m2 = (val / INT64CONST(100000)) % 1000;                         /* thousands */
798         m3 = (val / INT64CONST(100000000)) % 1000;                      /* millions */
799         m4 = (val / INT64CONST(100000000000)) % 1000;           /* billions */
800         m5 = (val / INT64CONST(100000000000000)) % 1000;        /* trillions */
801         m6 = (val / INT64CONST(100000000000000000)) % 1000;     /* quadrillions */
802
803         if (m6)
804         {
805                 strcat(buf, num_word(m6));
806                 strcat(buf, " quadrillion ");
807         }
808
809         if (m5)
810         {
811                 strcat(buf, num_word(m5));
812                 strcat(buf, " trillion ");
813         }
814
815         if (m4)
816         {
817                 strcat(buf, num_word(m4));
818                 strcat(buf, " billion ");
819         }
820
821         if (m3)
822         {
823                 strcat(buf, num_word(m3));
824                 strcat(buf, " million ");
825         }
826
827         if (m2)
828         {
829                 strcat(buf, num_word(m2));
830                 strcat(buf, " thousand ");
831         }
832
833         if (m1)
834                 strcat(buf, num_word(m1));
835
836         if (!*p)
837                 strcat(buf, "zero");
838
839         strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
840         strcat(buf, num_word(m0));
841         strcat(buf, m0 == 1 ? " cent" : " cents");
842
843         /* capitalize output */
844         buf[0] = pg_toupper((unsigned char) buf[0]);
845
846         /* return as text datum */
847         PG_RETURN_TEXT_P(cstring_to_text(buf));
848 }