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