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