OSDN Git Service

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