]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/cash.c
Simplify and standardize conversions between TEXT datums and ordinary C
[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.78 2008/03/25 22:42:43 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  * Cash is a pass-by-ref SQL type, so we must pass and return pointers.
39  * These macros and support routine hide the pass-by-refness.
40  */
41 #define PG_GETARG_CASH(n)  (* ((Cash *) PG_GETARG_POINTER(n)))
42 #define PG_RETURN_CASH(x)  return CashGetDatum(x)
43
44
45
46 /*************************************************************************
47  * Private routines
48  ************************************************************************/
49
50 static const char *
51 num_word(Cash value)
52 {
53         static char buf[128];
54         static const char *small[] = {
55                 "zero", "one", "two", "three", "four", "five", "six", "seven",
56                 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
57                 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
58                 "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"
59         };
60         const char **big = small + 18;
61         int                     tu = value % 100;
62
63         /* deal with the simple cases first */
64         if (value <= 20)
65                 return small[value];
66
67         /* is it an even multiple of 100? */
68         if (!tu)
69         {
70                 sprintf(buf, "%s hundred", small[value / 100]);
71                 return buf;
72         }
73
74         /* more than 99? */
75         if (value > 99)
76         {
77                 /* is it an even multiple of 10 other than 10? */
78                 if (value % 10 == 0 && tu > 10)
79                         sprintf(buf, "%s hundred %s",
80                                         small[value / 100], big[tu / 10]);
81                 else if (tu < 20)
82                         sprintf(buf, "%s hundred and %s",
83                                         small[value / 100], small[tu]);
84                 else
85                         sprintf(buf, "%s hundred %s %s",
86                                         small[value / 100], big[tu / 10], small[tu % 10]);
87         }
88         else
89         {
90                 /* is it an even multiple of 10 other than 10? */
91                 if (value % 10 == 0 && tu > 10)
92                         sprintf(buf, "%s", big[tu / 10]);
93                 else if (tu < 20)
94                         sprintf(buf, "%s", small[tu]);
95                 else
96                         sprintf(buf, "%s %s", big[tu / 10], small[tu % 10]);
97         }
98
99         return buf;
100 }       /* num_word() */
101
102 static Datum
103 CashGetDatum(Cash value)
104 {
105         Cash       *result = (Cash *) palloc(sizeof(Cash));
106
107         *result = value;
108         return PointerGetDatum(result);
109 }
110
111
112 /* cash_in()
113  * Convert a string to a cash data type.
114  * Format is [$]###[,]###[.##]
115  * Examples: 123.45 $123.45 $123,456.78
116  *
117  */
118 Datum
119 cash_in(PG_FUNCTION_ARGS)
120 {
121         char       *str = PG_GETARG_CSTRING(0);
122         Cash            result;
123         Cash            value = 0;
124         Cash            dec = 0;
125         Cash            sgn = 1;
126         int                     seen_dot = 0;
127         const char *s = str;
128         int                     fpoint;
129         char            dsymbol,
130                                 ssymbol,
131                                 psymbol;
132         const char *nsymbol,
133                            *csymbol;
134
135         struct lconv *lconvert = PGLC_localeconv();
136
137         /*
138          * frac_digits will be CHAR_MAX in some locales, notably C.  However, just
139          * testing for == CHAR_MAX is risky, because of compilers like gcc that
140          * "helpfully" let you alter the platform-standard definition of whether
141          * char is signed or not.  If we are so unfortunate as to get compiled
142          * with a nonstandard -fsigned-char or -funsigned-char switch, then our
143          * idea of CHAR_MAX will not agree with libc's. The safest course is not
144          * to test for CHAR_MAX at all, but to impose a range check for plausible
145          * frac_digits values.
146          */
147         fpoint = lconvert->frac_digits;
148         if (fpoint < 0 || fpoint > 10)
149                 fpoint = 2;                             /* best guess in this case, I think */
150
151         dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
152         if (*lconvert->mon_thousands_sep != '\0')
153                 ssymbol = *lconvert->mon_thousands_sep;
154         else
155                 /* ssymbol should not equal dsymbol */
156                 ssymbol = (dsymbol != ',') ? ',' : '.';
157         csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
158         psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
159         nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
160
161 #ifdef CASHDEBUG
162         printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
163                    fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
164 #endif
165
166         /* we need to add all sorts of checking here.  For now just */
167         /* strip all leading whitespace and any leading currency symbol */
168         while (isspace((unsigned char) *s))
169                 s++;
170         if (strncmp(s, csymbol, strlen(csymbol)) == 0)
171                 s += strlen(csymbol);
172
173 #ifdef CASHDEBUG
174         printf("cashin- string is '%s'\n", s);
175 #endif
176
177         /* a leading minus or paren signifies a negative number */
178         /* again, better heuristics needed */
179         /* XXX - doesn't properly check for balanced parens - djmc */
180         if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
181         {
182                 sgn = -1;
183                 s += strlen(nsymbol);
184 #ifdef CASHDEBUG
185                 printf("cashin- negative symbol; string is '%s'\n", s);
186 #endif
187         }
188         else if (*s == '(')
189         {
190                 sgn = -1;
191                 s++;
192         }
193         else if (*s == psymbol)
194                 s++;
195
196 #ifdef CASHDEBUG
197         printf("cashin- string is '%s'\n", s);
198 #endif
199
200         while (isspace((unsigned char) *s))
201                 s++;
202         if (strncmp(s, csymbol, strlen(csymbol)) == 0)
203                 s += strlen(csymbol);
204
205 #ifdef CASHDEBUG
206         printf("cashin- string is '%s'\n", s);
207 #endif
208
209         for (;; s++)
210         {
211                 /* we look for digits as int8 as we have less */
212                 /* than the required number of decimal places */
213                 if (isdigit((unsigned char) *s) && dec < fpoint)
214                 {
215                         value = (value * 10) + *s - '0';
216
217                         if (seen_dot)
218                                 dec++;
219
220                 }
221                 /* decimal point? then start counting fractions... */
222                 else if (*s == dsymbol && !seen_dot)
223                 {
224                         seen_dot = 1;
225
226                 }
227                 /* not "thousands" separator? */
228                 else if (*s != ssymbol)
229                 {
230                         /* round off */
231                         if (isdigit((unsigned char) *s) && *s >= '5')
232                                 value++;
233
234                         /* adjust for less than required decimal places */
235                         for (; dec < fpoint; dec++)
236                                 value *= 10;
237
238                         break;
239                 }
240         }
241
242         /* should only be trailing digits followed by whitespace or right paren */
243         while (isdigit((unsigned char) *s))
244                 s++;
245         while (isspace((unsigned char) *s) || *s == ')')
246                 s++;
247
248         if (*s != '\0')
249                 ereport(ERROR,
250                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
251                                  errmsg("invalid input syntax for type money: \"%s\"", str)));
252
253         result = value * sgn;
254
255 #ifdef CASHDEBUG
256         printf("cashin- result is %d\n", result);
257 #endif
258
259         PG_RETURN_CASH(result);
260 }
261
262
263 /* cash_out()
264  * Function to convert cash to a dollars and cents representation.
265  * XXX HACK This code appears to assume US conventions for
266  *      positive-valued amounts. - tgl 97/04/14
267  */
268 Datum
269 cash_out(PG_FUNCTION_ARGS)
270 {
271         Cash            value = PG_GETARG_CASH(0);
272         char       *result;
273         char            buf[CASH_BUFSZ];
274         int                     minus = 0;
275         int                     count = LAST_DIGIT;
276         int                     point_pos;
277         int                     ssymbol_position = 0;
278         int                     points,
279                                 mon_group;
280         char            ssymbol;
281         const char *csymbol,
282                            *nsymbol;
283         char            dsymbol;
284         char            convention;
285
286         struct lconv *lconvert = PGLC_localeconv();
287
288         /* see comments about frac_digits in cash_in() */
289         points = lconvert->frac_digits;
290         if (points < 0 || points > 10)
291                 points = 2;                             /* best guess in this case, I think */
292
293         /*
294          * As with frac_digits, must apply a range check to mon_grouping to avoid
295          * being fooled by variant CHAR_MAX values.
296          */
297         mon_group = *lconvert->mon_grouping;
298         if (mon_group <= 0 || mon_group > 6)
299                 mon_group = 3;
300
301         convention = lconvert->n_sign_posn;
302         dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
303         if (*lconvert->mon_thousands_sep != '\0')
304                 ssymbol = *lconvert->mon_thousands_sep;
305         else
306                 /* ssymbol should not equal dsymbol */
307                 ssymbol = (dsymbol != ',') ? ',' : '.';
308         csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
309         nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
310
311         point_pos = LAST_DIGIT - points;
312
313         point_pos -= (points - 1) / mon_group;
314         ssymbol_position = point_pos % (mon_group + 1);
315
316         /* we work with positive amounts and add the minus sign at the end */
317         if (value < 0)
318         {
319                 minus = 1;
320                 value = -value;
321         }
322
323         /* allow for trailing negative strings */
324         MemSet(buf, ' ', CASH_BUFSZ);
325         buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
326
327         while (value || count > (point_pos - 2))
328         {
329                 if (points && count == point_pos)
330                         buf[count--] = dsymbol;
331                 else if (ssymbol && count % (mon_group + 1) == ssymbol_position)
332                         buf[count--] = ssymbol;
333
334                 buf[count--] = ((uint64) value % 10) + '0';
335                 value = ((uint64) value) / 10;
336         }
337
338         strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
339         count -= strlen(csymbol) - 1;
340
341         /*
342          *      If points == 0 and the number of digits % mon_group == 0,
343          *      the code above adds a trailing ssymbol on the far right,
344          *      so remove it.
345          */
346         if (buf[LAST_DIGIT] == ssymbol)
347                 buf[LAST_DIGIT] = '\0';
348
349         /* see if we need to signify negative amount */
350         if (minus)
351         {
352                 result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
353
354                 /* Position code of 0 means use parens */
355                 if (convention == 0)
356                         sprintf(result, "(%s)", buf + count);
357                 else if (convention == 2)
358                         sprintf(result, "%s%s", buf + count, nsymbol);
359                 else
360                         sprintf(result, "%s%s", nsymbol, buf + count);
361         }
362         else
363         {
364                 result = palloc(CASH_BUFSZ + 2 - count);
365                 strcpy(result, buf + count);
366         }
367
368         PG_RETURN_CSTRING(result);
369 }
370
371 /*
372  *              cash_recv                       - converts external binary format to cash
373  */
374 Datum
375 cash_recv(PG_FUNCTION_ARGS)
376 {
377         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
378
379         PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
380 }
381
382 /*
383  *              cash_send                       - converts cash to binary format
384  */
385 Datum
386 cash_send(PG_FUNCTION_ARGS)
387 {
388         Cash            arg1 = PG_GETARG_CASH(0);
389         StringInfoData buf;
390
391         pq_begintypsend(&buf);
392         pq_sendint64(&buf, arg1);
393         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
394 }
395
396 /*
397  * Comparison functions
398  */
399
400 Datum
401 cash_eq(PG_FUNCTION_ARGS)
402 {
403         Cash            c1 = PG_GETARG_CASH(0);
404         Cash            c2 = PG_GETARG_CASH(1);
405
406         PG_RETURN_BOOL(c1 == c2);
407 }
408
409 Datum
410 cash_ne(PG_FUNCTION_ARGS)
411 {
412         Cash            c1 = PG_GETARG_CASH(0);
413         Cash            c2 = PG_GETARG_CASH(1);
414
415         PG_RETURN_BOOL(c1 != c2);
416 }
417
418 Datum
419 cash_lt(PG_FUNCTION_ARGS)
420 {
421         Cash            c1 = PG_GETARG_CASH(0);
422         Cash            c2 = PG_GETARG_CASH(1);
423
424         PG_RETURN_BOOL(c1 < c2);
425 }
426
427 Datum
428 cash_le(PG_FUNCTION_ARGS)
429 {
430         Cash            c1 = PG_GETARG_CASH(0);
431         Cash            c2 = PG_GETARG_CASH(1);
432
433         PG_RETURN_BOOL(c1 <= c2);
434 }
435
436 Datum
437 cash_gt(PG_FUNCTION_ARGS)
438 {
439         Cash            c1 = PG_GETARG_CASH(0);
440         Cash            c2 = PG_GETARG_CASH(1);
441
442         PG_RETURN_BOOL(c1 > c2);
443 }
444
445 Datum
446 cash_ge(PG_FUNCTION_ARGS)
447 {
448         Cash            c1 = PG_GETARG_CASH(0);
449         Cash            c2 = PG_GETARG_CASH(1);
450
451         PG_RETURN_BOOL(c1 >= c2);
452 }
453
454 Datum
455 cash_cmp(PG_FUNCTION_ARGS)
456 {
457         Cash            c1 = PG_GETARG_CASH(0);
458         Cash            c2 = PG_GETARG_CASH(1);
459
460         if (c1 > c2)
461                 PG_RETURN_INT32(1);
462         else if (c1 == c2)
463                 PG_RETURN_INT32(0);
464         else
465                 PG_RETURN_INT32(-1);
466 }
467
468
469 /* cash_pl()
470  * Add two cash values.
471  */
472 Datum
473 cash_pl(PG_FUNCTION_ARGS)
474 {
475         Cash            c1 = PG_GETARG_CASH(0);
476         Cash            c2 = PG_GETARG_CASH(1);
477         Cash            result;
478
479         result = c1 + c2;
480
481         PG_RETURN_CASH(result);
482 }
483
484
485 /* cash_mi()
486  * Subtract two cash values.
487  */
488 Datum
489 cash_mi(PG_FUNCTION_ARGS)
490 {
491         Cash            c1 = PG_GETARG_CASH(0);
492         Cash            c2 = PG_GETARG_CASH(1);
493         Cash            result;
494
495         result = c1 - c2;
496
497         PG_RETURN_CASH(result);
498 }
499
500
501 /* cash_mul_flt8()
502  * Multiply cash by float8.
503  */
504 Datum
505 cash_mul_flt8(PG_FUNCTION_ARGS)
506 {
507         Cash            c = PG_GETARG_CASH(0);
508         float8          f = PG_GETARG_FLOAT8(1);
509         Cash            result;
510
511         result = c * f;
512         PG_RETURN_CASH(result);
513 }
514
515
516 /* flt8_mul_cash()
517  * Multiply float8 by cash.
518  */
519 Datum
520 flt8_mul_cash(PG_FUNCTION_ARGS)
521 {
522         float8          f = PG_GETARG_FLOAT8(0);
523         Cash            c = PG_GETARG_CASH(1);
524         Cash            result;
525
526         result = f * c;
527         PG_RETURN_CASH(result);
528 }
529
530
531 /* cash_div_flt8()
532  * Divide cash by float8.
533  */
534 Datum
535 cash_div_flt8(PG_FUNCTION_ARGS)
536 {
537         Cash            c = PG_GETARG_CASH(0);
538         float8          f = PG_GETARG_FLOAT8(1);
539         Cash            result;
540
541         if (f == 0.0)
542                 ereport(ERROR,
543                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
544                                  errmsg("division by zero")));
545
546         result = rint(c / f);
547         PG_RETURN_CASH(result);
548 }
549
550
551 /* cash_mul_flt4()
552  * Multiply cash by float4.
553  */
554 Datum
555 cash_mul_flt4(PG_FUNCTION_ARGS)
556 {
557         Cash            c = PG_GETARG_CASH(0);
558         float4          f = PG_GETARG_FLOAT4(1);
559         Cash            result;
560
561         result = c * f;
562         PG_RETURN_CASH(result);
563 }
564
565
566 /* flt4_mul_cash()
567  * Multiply float4 by cash.
568  */
569 Datum
570 flt4_mul_cash(PG_FUNCTION_ARGS)
571 {
572         float4          f = PG_GETARG_FLOAT4(0);
573         Cash            c = PG_GETARG_CASH(1);
574         Cash            result;
575
576         result = f * c;
577         PG_RETURN_CASH(result);
578 }
579
580
581 /* cash_div_flt4()
582  * Divide cash by float4.
583  *
584  */
585 Datum
586 cash_div_flt4(PG_FUNCTION_ARGS)
587 {
588         Cash            c = PG_GETARG_CASH(0);
589         float4          f = PG_GETARG_FLOAT4(1);
590         Cash            result;
591
592         if (f == 0.0)
593                 ereport(ERROR,
594                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
595                                  errmsg("division by zero")));
596
597         result = rint(c / f);
598         PG_RETURN_CASH(result);
599 }
600
601
602 /* cash_mul_int8()
603  * Multiply cash by int8.
604  */
605 Datum
606 cash_mul_int8(PG_FUNCTION_ARGS)
607 {
608         Cash            c = PG_GETARG_CASH(0);
609         int64           i = PG_GETARG_INT64(1);
610         Cash            result;
611
612         result = c * i;
613         PG_RETURN_CASH(result);
614 }
615
616
617 /* int8_mul_cash()
618  * Multiply int8 by cash.
619  */
620 Datum
621 int8_mul_cash(PG_FUNCTION_ARGS)
622 {
623         int64           i = PG_GETARG_INT64(0);
624         Cash            c = PG_GETARG_CASH(1);
625         Cash            result;
626
627         result = i * c;
628         PG_RETURN_CASH(result);
629 }
630
631 /* cash_div_int8()
632  * Divide cash by 8-byte integer.
633  */
634 Datum
635 cash_div_int8(PG_FUNCTION_ARGS)
636 {
637         Cash            c = PG_GETARG_CASH(0);
638         int64           i = PG_GETARG_INT64(1);
639         Cash            result;
640
641         if (i == 0)
642                 ereport(ERROR,
643                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
644                                  errmsg("division by zero")));
645
646         result = rint(c / i);
647
648         PG_RETURN_CASH(result);
649 }
650
651
652 /* cash_mul_int4()
653  * Multiply cash by int4.
654  */
655 Datum
656 cash_mul_int4(PG_FUNCTION_ARGS)
657 {
658         Cash            c = PG_GETARG_CASH(0);
659         int32           i = PG_GETARG_INT32(1);
660         Cash            result;
661
662         result = c * i;
663         PG_RETURN_CASH(result);
664 }
665
666
667 /* int4_mul_cash()
668  * Multiply int4 by cash.
669  */
670 Datum
671 int4_mul_cash(PG_FUNCTION_ARGS)
672 {
673         int32           i = PG_GETARG_INT32(0);
674         Cash            c = PG_GETARG_CASH(1);
675         Cash            result;
676
677         result = i * c;
678         PG_RETURN_CASH(result);
679 }
680
681
682 /* cash_div_int4()
683  * Divide cash by 4-byte integer.
684  *
685  */
686 Datum
687 cash_div_int4(PG_FUNCTION_ARGS)
688 {
689         Cash            c = PG_GETARG_CASH(0);
690         int32           i = PG_GETARG_INT32(1);
691         Cash            result;
692
693         if (i == 0)
694                 ereport(ERROR,
695                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
696                                  errmsg("division by zero")));
697
698         result = rint(c / i);
699
700         PG_RETURN_CASH(result);
701 }
702
703
704 /* cash_mul_int2()
705  * Multiply cash by int2.
706  */
707 Datum
708 cash_mul_int2(PG_FUNCTION_ARGS)
709 {
710         Cash            c = PG_GETARG_CASH(0);
711         int16           s = PG_GETARG_INT16(1);
712         Cash            result;
713
714         result = c * s;
715         PG_RETURN_CASH(result);
716 }
717
718 /* int2_mul_cash()
719  * Multiply int2 by cash.
720  */
721 Datum
722 int2_mul_cash(PG_FUNCTION_ARGS)
723 {
724         int16           s = PG_GETARG_INT16(0);
725         Cash            c = PG_GETARG_CASH(1);
726         Cash            result;
727
728         result = s * c;
729         PG_RETURN_CASH(result);
730 }
731
732 /* cash_div_int2()
733  * Divide cash by int2.
734  *
735  */
736 Datum
737 cash_div_int2(PG_FUNCTION_ARGS)
738 {
739         Cash            c = PG_GETARG_CASH(0);
740         int16           s = PG_GETARG_INT16(1);
741         Cash            result;
742
743         if (s == 0)
744                 ereport(ERROR,
745                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
746                                  errmsg("division by zero")));
747
748         result = rint(c / s);
749         PG_RETURN_CASH(result);
750 }
751
752 /* cashlarger()
753  * Return larger of two cash values.
754  */
755 Datum
756 cashlarger(PG_FUNCTION_ARGS)
757 {
758         Cash            c1 = PG_GETARG_CASH(0);
759         Cash            c2 = PG_GETARG_CASH(1);
760         Cash            result;
761
762         result = (c1 > c2) ? c1 : c2;
763
764         PG_RETURN_CASH(result);
765 }
766
767 /* cashsmaller()
768  * Return smaller of two cash values.
769  */
770 Datum
771 cashsmaller(PG_FUNCTION_ARGS)
772 {
773         Cash            c1 = PG_GETARG_CASH(0);
774         Cash            c2 = PG_GETARG_CASH(1);
775         Cash            result;
776
777         result = (c1 < c2) ? c1 : c2;
778
779         PG_RETURN_CASH(result);
780 }
781
782 /* cash_words()
783  * This converts a int4 as well but to a representation using words
784  * Obviously way North American centric - sorry
785  */
786 Datum
787 cash_words(PG_FUNCTION_ARGS)
788 {
789         Cash            value = PG_GETARG_CASH(0);
790         uint64          val;
791         char            buf[256];
792         char       *p = buf;
793         Cash            m0;
794         Cash            m1;
795         Cash            m2;
796         Cash            m3;
797         Cash            m4;
798         Cash            m5;
799         Cash            m6;
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         /* return as text datum */
866         PG_RETURN_TEXT_P(cstring_to_text(buf));
867 }