]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/cash.c
Fix money type's send/receive functions to conform to recent widening
[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.72 2007/08/21 03:14:36 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/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         }
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         ssymbol = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
153         csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
154         psymbol = ((*lconvert->positive_sign != '\0') ? *lconvert->positive_sign : '+');
155         nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
156
157 #ifdef CASHDEBUG
158         printf("cashin- precision '%d'; decimal '%c'; thousands '%c'; currency '%s'; positive '%c'; negative '%s'\n",
159                    fpoint, dsymbol, ssymbol, csymbol, psymbol, nsymbol);
160 #endif
161
162         /* we need to add all sorts of checking here.  For now just */
163         /* strip all leading whitespace and any leading currency symbol */
164         while (isspace((unsigned char) *s))
165                 s++;
166         if (strncmp(s, csymbol, strlen(csymbol)) == 0)
167                 s += strlen(csymbol);
168
169 #ifdef CASHDEBUG
170         printf("cashin- string is '%s'\n", s);
171 #endif
172
173         /* a leading minus or paren signifies a negative number */
174         /* again, better heuristics needed */
175         /* XXX - doesn't properly check for balanced parens - djmc */
176         if (strncmp(s, nsymbol, strlen(nsymbol)) == 0)
177         {
178                 sgn = -1;
179                 s += strlen(nsymbol);
180 #ifdef CASHDEBUG
181                 printf("cashin- negative symbol; string is '%s'\n", s);
182 #endif
183         }
184         else if (*s == '(')
185         {
186                 sgn = -1;
187                 s++;
188
189         }
190         else if (*s == psymbol)
191                 s++;
192
193 #ifdef CASHDEBUG
194         printf("cashin- string is '%s'\n", s);
195 #endif
196
197         while (isspace((unsigned char) *s))
198                 s++;
199         if (strncmp(s, csymbol, strlen(csymbol)) == 0)
200                 s += strlen(csymbol);
201
202 #ifdef CASHDEBUG
203         printf("cashin- string is '%s'\n", s);
204 #endif
205
206         for (;; s++)
207         {
208                 /* we look for digits as int8 as we have less */
209                 /* than the required number of decimal places */
210                 if (isdigit((unsigned char) *s) && dec < fpoint)
211                 {
212                         value = (value * 10) + *s - '0';
213
214                         if (seen_dot)
215                                 dec++;
216
217                 }
218                 /* decimal point? then start counting fractions... */
219                 else if (*s == dsymbol && !seen_dot)
220                 {
221                         seen_dot = 1;
222
223                 }
224                 /* "thousands" separator? then skip... */
225                 else if (*s == ssymbol)
226                 {
227
228                 }
229                 else
230                 {
231                         /* round off */
232                         if (isdigit((unsigned char) *s) && *s >= '5')
233                                 value++;
234
235                         /* adjust for less than required decimal places */
236                         for (; dec < fpoint; dec++)
237                                 value *= 10;
238
239                         break;
240                 }
241         }
242
243         /* should only be trailing digits followed by whitespace or right paren */
244         while (isdigit((unsigned char) *s))
245                 s++;
246         while (isspace((unsigned char) *s) || *s == ')')
247                 s++;
248
249         if (*s != '\0')
250                 ereport(ERROR,
251                                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
252                                  errmsg("invalid input syntax for type money: \"%s\"", str)));
253
254         result = value * sgn;
255
256 #ifdef CASHDEBUG
257         printf("cashin- result is %d\n", result);
258 #endif
259
260         PG_RETURN_CASH(result);
261 }
262
263
264 /* cash_out()
265  * Function to convert cash to a dollars and cents representation.
266  * XXX HACK This code appears to assume US conventions for
267  *      positive-valued amounts. - tgl 97/04/14
268  */
269 Datum
270 cash_out(PG_FUNCTION_ARGS)
271 {
272         Cash            value = PG_GETARG_CASH(0);
273         char       *result;
274         char            buf[CASH_BUFSZ];
275         int                     minus = 0;
276         int                     count = LAST_DIGIT;
277         int                     point_pos;
278         int                     comma_position = 0;
279         int                     points,
280                                 mon_group;
281         char            comma;
282         const char *csymbol,
283                            *nsymbol;
284         char            dsymbol;
285         char            convention;
286
287         struct lconv *lconvert = PGLC_localeconv();
288
289         /* see comments about frac_digits in cash_in() */
290         points = lconvert->frac_digits;
291         if (points < 0 || points > 10)
292                 points = 2;                             /* best guess in this case, I think */
293
294         /*
295          * As with frac_digits, must apply a range check to mon_grouping to avoid
296          * being fooled by variant CHAR_MAX values.
297          */
298         mon_group = *lconvert->mon_grouping;
299         if (mon_group <= 0 || mon_group > 6)
300                 mon_group = 3;
301
302         comma = ((*lconvert->mon_thousands_sep != '\0') ? *lconvert->mon_thousands_sep : ',');
303         convention = lconvert->n_sign_posn;
304         dsymbol = ((*lconvert->mon_decimal_point != '\0') ? *lconvert->mon_decimal_point : '.');
305         csymbol = ((*lconvert->currency_symbol != '\0') ? lconvert->currency_symbol : "$");
306         nsymbol = ((*lconvert->negative_sign != '\0') ? lconvert->negative_sign : "-");
307
308         point_pos = LAST_DIGIT - points;
309
310         /* allow more than three decimal points and separate them */
311         if (comma)
312         {
313                 point_pos -= (points - 1) / mon_group;
314                 comma_position = point_pos % (mon_group + 1);
315         }
316
317         /* we work with positive amounts and add the minus sign at the end */
318         if (value < 0)
319         {
320                 minus = 1;
321                 value = -value;
322         }
323
324         /* allow for trailing negative strings */
325         MemSet(buf, ' ', CASH_BUFSZ);
326         buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
327
328         while (value || count > (point_pos - 2))
329         {
330                 if (points && count == point_pos)
331                         buf[count--] = dsymbol;
332                 else if (comma && count % (mon_group + 1) == comma_position)
333                         buf[count--] = comma;
334
335                 buf[count--] = ((uint64) value % 10) + '0';
336                 value = ((uint64) value) / 10;
337         }
338
339         strncpy((buf + count - strlen(csymbol) + 1), csymbol, strlen(csymbol));
340         count -= strlen(csymbol) - 1;
341
342         if (buf[LAST_DIGIT] == ',')
343                 buf[LAST_DIGIT] = buf[LAST_PAREN];
344
345         /* see if we need to signify negative amount */
346         if (minus)
347         {
348                 result = palloc(CASH_BUFSZ + 2 - count + strlen(nsymbol));
349
350                 /* Position code of 0 means use parens */
351                 if (convention == 0)
352                         sprintf(result, "(%s)", buf + count);
353                 else if (convention == 2)
354                         sprintf(result, "%s%s", buf + count, nsymbol);
355                 else
356                         sprintf(result, "%s%s", nsymbol, buf + count);
357         }
358         else
359         {
360                 result = palloc(CASH_BUFSZ + 2 - count);
361                 strcpy(result, buf + count);
362         }
363
364         PG_RETURN_CSTRING(result);
365 }
366
367 /*
368  *              cash_recv                       - converts external binary format to cash
369  */
370 Datum
371 cash_recv(PG_FUNCTION_ARGS)
372 {
373         StringInfo      buf = (StringInfo) PG_GETARG_POINTER(0);
374
375         PG_RETURN_CASH((Cash) pq_getmsgint64(buf));
376 }
377
378 /*
379  *              cash_send                       - converts cash to binary format
380  */
381 Datum
382 cash_send(PG_FUNCTION_ARGS)
383 {
384         Cash            arg1 = PG_GETARG_CASH(0);
385         StringInfoData buf;
386
387         pq_begintypsend(&buf);
388         pq_sendint64(&buf, arg1);
389         PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
390 }
391
392 /*
393  * Comparison functions
394  */
395
396 Datum
397 cash_eq(PG_FUNCTION_ARGS)
398 {
399         Cash            c1 = PG_GETARG_CASH(0);
400         Cash            c2 = PG_GETARG_CASH(1);
401
402         PG_RETURN_BOOL(c1 == c2);
403 }
404
405 Datum
406 cash_ne(PG_FUNCTION_ARGS)
407 {
408         Cash            c1 = PG_GETARG_CASH(0);
409         Cash            c2 = PG_GETARG_CASH(1);
410
411         PG_RETURN_BOOL(c1 != c2);
412 }
413
414 Datum
415 cash_lt(PG_FUNCTION_ARGS)
416 {
417         Cash            c1 = PG_GETARG_CASH(0);
418         Cash            c2 = PG_GETARG_CASH(1);
419
420         PG_RETURN_BOOL(c1 < c2);
421 }
422
423 Datum
424 cash_le(PG_FUNCTION_ARGS)
425 {
426         Cash            c1 = PG_GETARG_CASH(0);
427         Cash            c2 = PG_GETARG_CASH(1);
428
429         PG_RETURN_BOOL(c1 <= c2);
430 }
431
432 Datum
433 cash_gt(PG_FUNCTION_ARGS)
434 {
435         Cash            c1 = PG_GETARG_CASH(0);
436         Cash            c2 = PG_GETARG_CASH(1);
437
438         PG_RETURN_BOOL(c1 > c2);
439 }
440
441 Datum
442 cash_ge(PG_FUNCTION_ARGS)
443 {
444         Cash            c1 = PG_GETARG_CASH(0);
445         Cash            c2 = PG_GETARG_CASH(1);
446
447         PG_RETURN_BOOL(c1 >= c2);
448 }
449
450 Datum
451 cash_cmp(PG_FUNCTION_ARGS)
452 {
453         Cash            c1 = PG_GETARG_CASH(0);
454         Cash            c2 = PG_GETARG_CASH(1);
455
456         if (c1 > c2)
457                 PG_RETURN_INT32(1);
458         else if (c1 == c2)
459                 PG_RETURN_INT32(0);
460         else
461                 PG_RETURN_INT32(-1);
462 }
463
464
465 /* cash_pl()
466  * Add two cash values.
467  */
468 Datum
469 cash_pl(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_mi()
482  * Subtract two cash values.
483  */
484 Datum
485 cash_mi(PG_FUNCTION_ARGS)
486 {
487         Cash            c1 = PG_GETARG_CASH(0);
488         Cash            c2 = PG_GETARG_CASH(1);
489         Cash            result;
490
491         result = c1 - c2;
492
493         PG_RETURN_CASH(result);
494 }
495
496
497 /* cash_mul_flt8()
498  * Multiply cash by float8.
499  */
500 Datum
501 cash_mul_flt8(PG_FUNCTION_ARGS)
502 {
503         Cash            c = PG_GETARG_CASH(0);
504         float8          f = PG_GETARG_FLOAT8(1);
505         Cash            result;
506
507         result = c * f;
508         PG_RETURN_CASH(result);
509 }
510
511
512 /* flt8_mul_cash()
513  * Multiply float8 by cash.
514  */
515 Datum
516 flt8_mul_cash(PG_FUNCTION_ARGS)
517 {
518         float8          f = PG_GETARG_FLOAT8(0);
519         Cash            c = PG_GETARG_CASH(1);
520         Cash            result;
521
522         result = f * c;
523         PG_RETURN_CASH(result);
524 }
525
526
527 /* cash_div_flt8()
528  * Divide cash by float8.
529  */
530 Datum
531 cash_div_flt8(PG_FUNCTION_ARGS)
532 {
533         Cash            c = PG_GETARG_CASH(0);
534         float8          f = PG_GETARG_FLOAT8(1);
535         Cash            result;
536
537         if (f == 0.0)
538                 ereport(ERROR,
539                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
540                                  errmsg("division by zero")));
541
542         result = rint(c / f);
543         PG_RETURN_CASH(result);
544 }
545
546
547 /* cash_mul_flt4()
548  * Multiply cash by float4.
549  */
550 Datum
551 cash_mul_flt4(PG_FUNCTION_ARGS)
552 {
553         Cash            c = PG_GETARG_CASH(0);
554         float4          f = PG_GETARG_FLOAT4(1);
555         Cash            result;
556
557         result = c * f;
558         PG_RETURN_CASH(result);
559 }
560
561
562 /* flt4_mul_cash()
563  * Multiply float4 by cash.
564  */
565 Datum
566 flt4_mul_cash(PG_FUNCTION_ARGS)
567 {
568         float4          f = PG_GETARG_FLOAT4(0);
569         Cash            c = PG_GETARG_CASH(1);
570         Cash            result;
571
572         result = f * c;
573         PG_RETURN_CASH(result);
574 }
575
576
577 /* cash_div_flt4()
578  * Divide cash by float4.
579  *
580  */
581 Datum
582 cash_div_flt4(PG_FUNCTION_ARGS)
583 {
584         Cash            c = PG_GETARG_CASH(0);
585         float4          f = PG_GETARG_FLOAT4(1);
586         Cash            result;
587
588         if (f == 0.0)
589                 ereport(ERROR,
590                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
591                                  errmsg("division by zero")));
592
593         result = rint(c / f);
594         PG_RETURN_CASH(result);
595 }
596
597
598 /* cash_mul_int8()
599  * Multiply cash by int8.
600  */
601 Datum
602 cash_mul_int8(PG_FUNCTION_ARGS)
603 {
604         Cash            c = PG_GETARG_CASH(0);
605         int64           i = PG_GETARG_INT64(1);
606         Cash            result;
607
608         result = c * i;
609         PG_RETURN_CASH(result);
610 }
611
612
613 /* int8_mul_cash()
614  * Multiply int8 by cash.
615  */
616 Datum
617 int8_mul_cash(PG_FUNCTION_ARGS)
618 {
619         int64           i = PG_GETARG_INT64(0);
620         Cash            c = PG_GETARG_CASH(1);
621         Cash            result;
622
623         result = i * c;
624         PG_RETURN_CASH(result);
625 }
626
627 /* cash_div_int8()
628  * Divide cash by 8-byte integer.
629  */
630 Datum
631 cash_div_int8(PG_FUNCTION_ARGS)
632 {
633         Cash            c = PG_GETARG_CASH(0);
634         int64           i = PG_GETARG_INT64(1);
635         Cash            result;
636
637         if (i == 0)
638                 ereport(ERROR,
639                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
640                                  errmsg("division by zero")));
641
642         result = rint(c / i);
643
644         PG_RETURN_CASH(result);
645 }
646
647
648 /* cash_mul_int4()
649  * Multiply cash by int4.
650  */
651 Datum
652 cash_mul_int4(PG_FUNCTION_ARGS)
653 {
654         Cash            c = PG_GETARG_CASH(0);
655         int64           i = PG_GETARG_INT64(1);
656         Cash            result;
657
658         result = c * i;
659         PG_RETURN_CASH(result);
660 }
661
662
663 /* int4_mul_cash()
664  * Multiply int4 by cash.
665  */
666 Datum
667 int4_mul_cash(PG_FUNCTION_ARGS)
668 {
669         int32           i = PG_GETARG_INT32(0);
670         Cash            c = PG_GETARG_CASH(1);
671         Cash            result;
672
673         result = i * c;
674         PG_RETURN_CASH(result);
675 }
676
677
678 /* cash_div_int4()
679  * Divide cash by 4-byte integer.
680  *
681  */
682 Datum
683 cash_div_int4(PG_FUNCTION_ARGS)
684 {
685         Cash            c = PG_GETARG_CASH(0);
686         int64           i = PG_GETARG_INT64(1);
687         Cash            result;
688
689         if (i == 0)
690                 ereport(ERROR,
691                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
692                                  errmsg("division by zero")));
693
694         result = rint(c / i);
695
696         PG_RETURN_CASH(result);
697 }
698
699
700 /* cash_mul_int2()
701  * Multiply cash by int2.
702  */
703 Datum
704 cash_mul_int2(PG_FUNCTION_ARGS)
705 {
706         Cash            c = PG_GETARG_CASH(0);
707         int16           s = PG_GETARG_INT16(1);
708         Cash            result;
709
710         result = c * s;
711         PG_RETURN_CASH(result);
712 }
713
714 /* int2_mul_cash()
715  * Multiply int2 by cash.
716  */
717 Datum
718 int2_mul_cash(PG_FUNCTION_ARGS)
719 {
720         int16           s = PG_GETARG_INT16(0);
721         Cash            c = PG_GETARG_CASH(1);
722         Cash            result;
723
724         result = s * c;
725         PG_RETURN_CASH(result);
726 }
727
728 /* cash_div_int2()
729  * Divide cash by int2.
730  *
731  */
732 Datum
733 cash_div_int2(PG_FUNCTION_ARGS)
734 {
735         Cash            c = PG_GETARG_CASH(0);
736         int16           s = PG_GETARG_INT16(1);
737         Cash            result;
738
739         if (s == 0)
740                 ereport(ERROR,
741                                 (errcode(ERRCODE_DIVISION_BY_ZERO),
742                                  errmsg("division by zero")));
743
744         result = rint(c / s);
745         PG_RETURN_CASH(result);
746 }
747
748 /* cashlarger()
749  * Return larger of two cash values.
750  */
751 Datum
752 cashlarger(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 /* cashsmaller()
764  * Return smaller of two cash values.
765  */
766 Datum
767 cashsmaller(PG_FUNCTION_ARGS)
768 {
769         Cash            c1 = PG_GETARG_CASH(0);
770         Cash            c2 = PG_GETARG_CASH(1);
771         Cash            result;
772
773         result = (c1 < c2) ? c1 : c2;
774
775         PG_RETURN_CASH(result);
776 }
777
778 /* cash_words()
779  * This converts a int4 as well but to a representation using words
780  * Obviously way North American centric - sorry
781  */
782 Datum
783 cash_words(PG_FUNCTION_ARGS)
784 {
785         Cash            value = PG_GETARG_CASH(0);
786         uint64 val;
787         char            buf[256];
788         char       *p = buf;
789         Cash            m0;
790         Cash            m1;
791         Cash            m2;
792         Cash            m3;
793         Cash            m4;
794         Cash            m5;
795         Cash            m6;
796         text       *result;
797
798         /* work with positive numbers */
799         if (value < 0)
800         {
801                 value = -value;
802                 strcpy(buf, "minus ");
803                 p += 6;
804         }
805         else
806                 buf[0] = '\0';
807
808         /* Now treat as unsigned, to avoid trouble at INT_MIN */
809         val = (uint64) value;
810
811         m0 = val % 100ll;                               /* cents */
812         m1 = (val / 100ll) % 1000;      /* hundreds */
813         m2 = (val / 100000ll) % 1000; /* thousands */
814         m3 = val / 100000000ll % 1000;  /* millions */
815         m4 = val / 100000000000ll % 1000;       /* billions */
816         m5 = val / 100000000000000ll % 1000;    /* trillions */
817         m6 = val / 100000000000000000ll % 1000; /* quadrillions */
818
819         if (m6)
820         {
821                 strcat(buf, num_word(m6));
822                 strcat(buf, " quadrillion ");
823         }
824
825         if (m5)
826         {
827                 strcat(buf, num_word(m5));
828                 strcat(buf, " trillion ");
829         }
830
831         if (m4)
832         {
833                 strcat(buf, num_word(m4));
834                 strcat(buf, " billion ");
835         }
836
837         if (m3)
838         {
839                 strcat(buf, num_word(m3));
840                 strcat(buf, " million ");
841         }
842
843         if (m2)
844         {
845                 strcat(buf, num_word(m2));
846                 strcat(buf, " thousand ");
847         }
848
849         if (m1)
850                 strcat(buf, num_word(m1));
851
852         if (!*p)
853                 strcat(buf, "zero");
854
855         strcat(buf, (val / 100) == 1 ? " dollar and " : " dollars and ");
856         strcat(buf, num_word(m0));
857         strcat(buf, m0 == 1 ? " cent" : " cents");
858
859         /* capitalize output */
860         buf[0] = pg_toupper((unsigned char) buf[0]);
861
862         /* make a text type for output */
863         result = (text *) palloc(strlen(buf) + VARHDRSZ);
864         SET_VARSIZE(result, strlen(buf) + VARHDRSZ);
865         memcpy(VARDATA(result), buf, strlen(buf));
866
867         PG_RETURN_TEXT_P(result);
868 }