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