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