]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/cash.c
Update copyright for the year 2010.
[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.82 2009/06/11 14:49:03 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/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, the code
324          * above adds a trailing ssymbol on the far right, so remove it.
325          */
326         if (buf[LAST_DIGIT] == ssymbol)
327                 buf[LAST_DIGIT] = '\0';
328
329         /* see if we need to signify negative amount */
330         if (minus)
331         {
332                 result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
333
334                 /* Position code of 0 means use parens */
335                 if (convention == 0)
336                         sprintf(result, "(%s)", buf + count);
337                 else if (convention == 2)
338                         sprintf(result, "%s%s", buf + count, nsymbol);
339                 else
340                         sprintf(result, "%s%s", nsymbol, buf + count);
341         }
342         else
343         {
344                 result = palloc(CASH_BUFSZ + 2 - count);
345                 strcpy(result, buf + count);
346         }
347
348         PG_RETURN_CSTRING(result);
349 }
350
351 /*
352  *              cash_recv                       - converts external binary format to cash
353  */
354 Datum
355 cash_recv(PG_FUNCTION_ARGS)
356 {
357         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
358
359         PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
360 }
361
362 /*
363  *              cash_send                       - converts cash to binary format
364  */
365 Datum
366 cash_send(PG_FUNCTION_ARGS)
367 {
368         Cash            arg1 = PG_GETARG_CASH(0);
369         StringInfoData buf;
370
371         pq_begintypsend(&buf);
372         pq_sendint64(&buf, arg1);
373         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
374 }
375
376 /*
377  * Comparison functions
378  */
379
380 Datum
381 cash_eq(PG_FUNCTION_ARGS)
382 {
383         Cash            c1 = PG_GETARG_CASH(0);
384         Cash            c2 = PG_GETARG_CASH(1);
385
386         PG_RETURN_BOOL(c1 == c2);
387 }
388
389 Datum
390 cash_ne(PG_FUNCTION_ARGS)
391 {
392         Cash            c1 = PG_GETARG_CASH(0);
393         Cash            c2 = PG_GETARG_CASH(1);
394
395         PG_RETURN_BOOL(c1 != c2);
396 }
397
398 Datum
399 cash_lt(PG_FUNCTION_ARGS)
400 {
401         Cash            c1 = PG_GETARG_CASH(0);
402         Cash            c2 = PG_GETARG_CASH(1);
403
404         PG_RETURN_BOOL(c1 < c2);
405 }
406
407 Datum
408 cash_le(PG_FUNCTION_ARGS)
409 {
410         Cash            c1 = PG_GETARG_CASH(0);
411         Cash            c2 = PG_GETARG_CASH(1);
412
413         PG_RETURN_BOOL(c1 <= c2);
414 }
415
416 Datum
417 cash_gt(PG_FUNCTION_ARGS)
418 {
419         Cash            c1 = PG_GETARG_CASH(0);
420         Cash            c2 = PG_GETARG_CASH(1);
421
422         PG_RETURN_BOOL(c1 > c2);
423 }
424
425 Datum
426 cash_ge(PG_FUNCTION_ARGS)
427 {
428         Cash            c1 = PG_GETARG_CASH(0);
429         Cash            c2 = PG_GETARG_CASH(1);
430
431         PG_RETURN_BOOL(c1 >= c2);
432 }
433
434 Datum
435 cash_cmp(PG_FUNCTION_ARGS)
436 {
437         Cash            c1 = PG_GETARG_CASH(0);
438         Cash            c2 = PG_GETARG_CASH(1);
439
440         if (c1 > c2)
441                 PG_RETURN_INT32(1);
442         else if (c1 == c2)
443                 PG_RETURN_INT32(0);
444         else
445                 PG_RETURN_INT32(-1);
446 }
447
448
449 /* cash_pl()
450  * Add two cash values.
451  */
452 Datum
453 cash_pl(PG_FUNCTION_ARGS)
454 {
455         Cash            c1 = PG_GETARG_CASH(0);
456         Cash            c2 = PG_GETARG_CASH(1);
457         Cash            result;
458
459         result = c1 + c2;
460
461         PG_RETURN_CASH(result);
462 }
463
464
465 /* cash_mi()
466  * Subtract two cash values.
467  */
468 Datum
469 cash_mi(PG_FUNCTION_ARGS)
470 {
471         Cash            c1 = PG_GETARG_CASH(0);
472         Cash            c2 = PG_GETARG_CASH(1);
473         Cash            result;
474
475         result = c1 - c2;
476
477         PG_RETURN_CASH(result);
478 }
479
480
481 /* cash_mul_flt8()
482  * Multiply cash by float8.
483  */
484 Datum
485 cash_mul_flt8(PG_FUNCTION_ARGS)
486 {
487         Cash            c = PG_GETARG_CASH(0);
488         float8          f = PG_GETARG_FLOAT8(1);
489         Cash            result;
490
491         result = c * f;
492         PG_RETURN_CASH(result);
493 }
494
495
496 /* flt8_mul_cash()
497  * Multiply float8 by cash.
498  */
499 Datum
500 flt8_mul_cash(PG_FUNCTION_ARGS)
501 {
502         float8          f = PG_GETARG_FLOAT8(0);
503         Cash            c = PG_GETARG_CASH(1);
504         Cash            result;
505
506         result = f * c;
507         PG_RETURN_CASH(result);
508 }
509
510
511 /* cash_div_flt8()
512  * Divide cash by float8.
513  */
514 Datum
515 cash_div_flt8(PG_FUNCTION_ARGS)
516 {
517         Cash            c = PG_GETARG_CASH(0);
518         float8          f = PG_GETARG_FLOAT8(1);
519         Cash            result;
520
521         if (f == 0.0)
522                 ereport(ERROR,
523                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
524                                  errmsg("division by zero")));
525
526         result = rint(c / f);
527         PG_RETURN_CASH(result);
528 }
529
530
531 /* cash_mul_flt4()
532  * Multiply cash by float4.
533  */
534 Datum
535 cash_mul_flt4(PG_FUNCTION_ARGS)
536 {
537         Cash            c = PG_GETARG_CASH(0);
538         float4          f = PG_GETARG_FLOAT4(1);
539         Cash            result;
540
541         result = c * f;
542         PG_RETURN_CASH(result);
543 }
544
545
546 /* flt4_mul_cash()
547  * Multiply float4 by cash.
548  */
549 Datum
550 flt4_mul_cash(PG_FUNCTION_ARGS)
551 {
552         float4          f = PG_GETARG_FLOAT4(0);
553         Cash            c = PG_GETARG_CASH(1);
554         Cash            result;
555
556         result = f * c;
557         PG_RETURN_CASH(result);
558 }
559
560
561 /* cash_div_flt4()
562  * Divide cash by float4.
563  *
564  */
565 Datum
566 cash_div_flt4(PG_FUNCTION_ARGS)
567 {
568         Cash            c = PG_GETARG_CASH(0);
569         float4          f = PG_GETARG_FLOAT4(1);
570         Cash            result;
571
572         if (f == 0.0)
573                 ereport(ERROR,
574                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
575                                  errmsg("division by zero")));
576
577         result = rint(c / f);
578         PG_RETURN_CASH(result);
579 }
580
581
582 /* cash_mul_int8()
583  * Multiply cash by int8.
584  */
585 Datum
586 cash_mul_int8(PG_FUNCTION_ARGS)
587 {
588         Cash            c = PG_GETARG_CASH(0);
589         int64           i = PG_GETARG_INT64(1);
590         Cash            result;
591
592         result = c * i;
593         PG_RETURN_CASH(result);
594 }
595
596
597 /* int8_mul_cash()
598  * Multiply int8 by cash.
599  */
600 Datum
601 int8_mul_cash(PG_FUNCTION_ARGS)
602 {
603         int64           i = PG_GETARG_INT64(0);
604         Cash            c = PG_GETARG_CASH(1);
605         Cash            result;
606
607         result = i * c;
608         PG_RETURN_CASH(result);
609 }
610
611 /* cash_div_int8()
612  * Divide cash by 8-byte integer.
613  */
614 Datum
615 cash_div_int8(PG_FUNCTION_ARGS)
616 {
617         Cash            c = PG_GETARG_CASH(0);
618         int64           i = PG_GETARG_INT64(1);
619         Cash            result;
620
621         if (i == 0)
622                 ereport(ERROR,
623                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
624                                  errmsg("division by zero")));
625
626         result = rint(c / i);
627
628         PG_RETURN_CASH(result);
629 }
630
631
632 /* cash_mul_int4()
633  * Multiply cash by int4.
634  */
635 Datum
636 cash_mul_int4(PG_FUNCTION_ARGS)
637 {
638         Cash            c = PG_GETARG_CASH(0);
639         int32           i = PG_GETARG_INT32(1);
640         Cash            result;
641
642         result = c * i;
643         PG_RETURN_CASH(result);
644 }
645
646
647 /* int4_mul_cash()
648  * Multiply int4 by cash.
649  */
650 Datum
651 int4_mul_cash(PG_FUNCTION_ARGS)
652 {
653         int32           i = PG_GETARG_INT32(0);
654         Cash            c = PG_GETARG_CASH(1);
655         Cash            result;
656
657         result = i * c;
658         PG_RETURN_CASH(result);
659 }
660
661
662 /* cash_div_int4()
663  * Divide cash by 4-byte integer.
664  *
665  */
666 Datum
667 cash_div_int4(PG_FUNCTION_ARGS)
668 {
669         Cash            c = PG_GETARG_CASH(0);
670         int32           i = PG_GETARG_INT32(1);
671         Cash            result;
672
673         if (i == 0)
674                 ereport(ERROR,
675                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
676                                  errmsg("division by zero")));
677
678         result = rint(c / i);
679
680         PG_RETURN_CASH(result);
681 }
682
683
684 /* cash_mul_int2()
685  * Multiply cash by int2.
686  */
687 Datum
688 cash_mul_int2(PG_FUNCTION_ARGS)
689 {
690         Cash            c = PG_GETARG_CASH(0);
691         int16           s = PG_GETARG_INT16(1);
692         Cash            result;
693
694         result = c * s;
695         PG_RETURN_CASH(result);
696 }
697
698 /* int2_mul_cash()
699  * Multiply int2 by cash.
700  */
701 Datum
702 int2_mul_cash(PG_FUNCTION_ARGS)
703 {
704         int16           s = PG_GETARG_INT16(0);
705         Cash            c = PG_GETARG_CASH(1);
706         Cash            result;
707
708         result = s * c;
709         PG_RETURN_CASH(result);
710 }
711
712 /* cash_div_int2()
713  * Divide cash by int2.
714  *
715  */
716 Datum
717 cash_div_int2(PG_FUNCTION_ARGS)
718 {
719         Cash            c = PG_GETARG_CASH(0);
720         int16           s = PG_GETARG_INT16(1);
721         Cash            result;
722
723         if (s == 0)
724                 ereport(ERROR,
725                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
726                                  errmsg("division by zero")));
727
728         result = rint(c / s);
729         PG_RETURN_CASH(result);
730 }
731
732 /* cashlarger()
733  * Return larger of two cash values.
734  */
735 Datum
736 cashlarger(PG_FUNCTION_ARGS)
737 {
738         Cash            c1 = PG_GETARG_CASH(0);
739         Cash            c2 = PG_GETARG_CASH(1);
740         Cash            result;
741
742         result = (c1 > c2) ? c1 : c2;
743
744         PG_RETURN_CASH(result);
745 }
746
747 /* cashsmaller()
748  * Return smaller of two cash values.
749  */
750 Datum
751 cashsmaller(PG_FUNCTION_ARGS)
752 {
753         Cash            c1 = PG_GETARG_CASH(0);
754         Cash            c2 = PG_GETARG_CASH(1);
755         Cash            result;
756
757         result = (c1 < c2) ? c1 : c2;
758
759         PG_RETURN_CASH(result);
760 }
761
762 /* cash_words()
763  * This converts a int4 as well but to a representation using words
764  * Obviously way North American centric - sorry
765  */
766 Datum
767 cash_words(PG_FUNCTION_ARGS)
768 {
769         Cash            value = PG_GETARG_CASH(0);
770         uint64          val;
771         char            buf[256];
772         char       *p = buf;
773         Cash            m0;
774         Cash            m1;
775         Cash            m2;
776         Cash            m3;
777         Cash            m4;
778         Cash            m5;
779         Cash            m6;
780
781         /* work with positive numbers */
782         if (value < 0)
783         {
784                 value = -value;
785                 strcpy(buf, "minus ");
786                 p += 6;
787         }
788         else
789                 buf[0] = '\0';
790
791         /* Now treat as unsigned, to avoid trouble at INT_MIN */
792         val = (uint64) value;
793
794         m0 = val % INT64CONST(100); /* cents */
795         m1 = (val / INT64CONST(100)) % 1000;            /* hundreds */
796         m2 = (val / INT64CONST(100000)) % 1000;         /* thousands */
797         m3 = (val / INT64CONST(100000000)) % 1000;      /* millions */
798         m4 = (val / INT64CONST(100000000000)) % 1000;           /* billions */
799         m5 = (val / INT64CONST(100000000000000)) % 1000;        /* trillions */
800         m6 = (val / INT64CONST(100000000000000000)) % 1000; /* quadrillions */
801
802         if (m6)
803         {
804                 strcat(buf, num_word(m6));
805                 strcat(buf, " quadrillion ");
806         }
807
808         if (m5)
809         {
810                 strcat(buf, num_word(m5));
811                 strcat(buf, " trillion ");
812         }
813
814         if (m4)
815         {
816                 strcat(buf, num_word(m4));
817                 strcat(buf, " billion ");
818         }
819
820         if (m3)
821         {
822                 strcat(buf, num_word(m3));
823                 strcat(buf, " million ");
824         }
825
826         if (m2)
827         {
828                 strcat(buf, num_word(m2));
829                 strcat(buf, " thousand ");
830         }
831
832         if (m1)
833                 strcat(buf, num_word(m1));
834
835         if (!*p)
836                 strcat(buf, "zero");
837
838         strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
839         strcat(buf, num_word(m0));
840         strcat(buf, m0 == 1 ? " cent" : " cents");
841
842         /* capitalize output */
843         buf[0] = pg_toupper((unsigned char) buf[0]);
844
845         /* return as text datum */
846         PG_RETURN_TEXT_P(cstring_to_text(buf));
847 }