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