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