]> granicus.if.org Git - postgresql/blob - src/backend/utils/adt/cash.c
Add in D'Arcy's cash 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 longs
7
8 Set tabstops to 4 for best results
9
10 A slightly modified version of this file and a discussion of the
11 workings can be found in the book "Software Solutions in C" by
12 Dale Schumacher, Academic Press, ISBN: 0-12-632360-7.
13
14 */
15
16 #include        <stdio.h>
17 #include        <string.h>
18 #include        <limits.h>
19 #include        <ctype.h>
20 #include        <locale.h>
21
22 #ifdef          TEST_MAIN
23 # include       <stdlib.h>
24 # define        palloc malloc
25 #else
26 # include       <palloc.h>
27 #endif
28
29 #include        "cash.h"
30
31 /* when we go to 64 bit values we will have to modify this */
32 #define         CASH_BUFSZ      24
33
34 #define         TERMINATOR      (CASH_BUFSZ - 1)
35 #define         LAST_PAREN      (TERMINATOR - 1)
36 #define         LAST_DIGIT      (LAST_PAREN - 1)
37
38 /* function to convert a long to a dollars and cents representation */
39 const char *
40 cash_out(long value)
41 {
42         char                    *retbuf, buf[CASH_BUFSZ];
43         struct lconv    *lc = localeconv();
44         int                             mod_group = *lc->mon_grouping;
45         int                             comma = *lc->mon_thousands_sep;
46         int                             points = lc->frac_digits;               /* int_frac_digits? */
47         int                             minus = 0;
48         int                             count = LAST_DIGIT;
49         int                             point_pos;
50         int                             comma_position = 0;
51
52         /* frac_digits in the C locale seems to return CHAR_MAX */
53         /* best guess is 2 in this case I think */
54         if (points == CHAR_MAX)
55                 points = 2;
56
57         point_pos = LAST_DIGIT - points;
58
59         /* We're playing a little fast and loose with this.  Shoot me. */
60         if (!mod_group || mod_group == CHAR_MAX)
61                 mod_group = 3;
62
63         /* allow more than three decimal points and separate them */
64         if (comma)
65         {
66                 point_pos -= (points - 1)/mod_group;
67                 comma_position = point_pos % (mod_group + 1);
68         }
69
70         /* we work with positive amounts and add the minus sign at the end */
71         if (value < 0)
72         {
73                 minus = 1;
74                 value *= -1;
75         }
76
77         /* allow for trailing negative strings */
78         memset(buf, ' ', CASH_BUFSZ);
79         buf[TERMINATOR] = buf[LAST_PAREN] = 0;
80
81         while (value || count > (point_pos - 2))
82         {
83                 if (points && count == point_pos)
84                         buf[count--] = *lc->decimal_point;
85                 else if (comma && count % (mod_group + 1) == comma_position)
86                         buf[count--] = comma;
87
88                 buf[count--] = (value % 10) + '0';
89                 value /= 10;
90         }
91
92         if (buf[LAST_DIGIT] == ',')
93                 buf[LAST_DIGIT] = buf[LAST_PAREN];
94
95         /* see if we need to signify negative amount */
96         if (minus)
97         {
98                 retbuf = palloc(CASH_BUFSZ + 2 - count + strlen(lc->negative_sign));
99
100                 /* Position code of 0 means use parens */
101                 if (!lc->n_sign_posn)
102                         sprintf(retbuf, "(%s)", buf + count);
103                 else if (lc->n_sign_posn == 2)
104                         sprintf(retbuf, "%s%s", buf + count, lc->negative_sign);
105                 else
106                         sprintf(retbuf, "%s%s", lc->negative_sign, buf + count);
107         }
108         else
109         {
110                 retbuf = palloc(CASH_BUFSZ + 2 - count);
111                 strcpy(retbuf, buf + count);
112         }
113
114         return retbuf;
115 }
116
117 /* convert a string to a long integer */
118 long
119 cash_in(const char *s)
120 {
121         long                    value = 0;
122         long                    dec = 0;
123         long                    sgn = 1;
124         int                             seen_dot = 0;
125         struct lconv    *lc = localeconv();
126         int                             fpoint = lc->frac_digits;               /* int_frac_digits? */
127
128         /* we need to add all sorts of checking here.  For now just */
129         /* strip all leading whitespace and any leading dollar sign */
130         while (isspace(*s) || *s == '$')
131                 s++;
132
133         /* a leading minus or paren signifies a negative number */
134         /* again, better heuristics needed */
135         if (*s == '-' || *s == '(')
136         {
137                 sgn = -1;
138                 s++;
139         }
140         else if (*s == '+')
141                 s++;
142
143         /* frac_digits in the C locale seems to return CHAR_MAX */
144         /* best guess is 2 in this case I think */
145         if (fpoint == CHAR_MAX)
146                 fpoint = 2;
147
148         for (; ; s++)
149         {
150                 /* we look for digits as long as we have less */
151                 /* than the required number of decimal places */
152                 if (isdigit(*s) && dec < fpoint)
153                 {
154                         value = (value * 10) + *s - '0';
155
156                         if (seen_dot)
157                                 dec++;
158                 }
159                 else if (*s == *lc->decimal_point && !seen_dot)
160                         seen_dot = 1;
161                 else
162                 {
163                         /* round off */
164                         if (isdigit(*s) && *s >= '5')
165                                 value++;
166
167                         /* adjust for less than required decimal places */
168                         for (; dec < fpoint; dec++)
169                                 value *= 10;
170
171                         return(value * sgn);
172                 }
173         }
174 }
175
176
177 /* used by cash_words_out() below */
178 static const char *
179 num_word(int value)
180 {
181         static char     buf[128];
182         static const char       *small[] = {
183                 "zero", "one", "two", "three", "four", "five", "six", "seven",
184                 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
185                 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
186                 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
187         };
188         const char      **big = small + 18;
189         int                     tu = value % 100;
190
191         /* deal with the simple cases first */
192         if (value <= 20)
193                 return(small[value]);
194
195         /* is it an even multiple of 100? */
196         if (!tu)
197         {
198                 sprintf(buf, "%s hundred", small[value/100]);
199                 return(buf);
200         }
201
202         /* more than 99? */
203         if (value > 99)
204         {
205                 /* is it an even multiple of 10 other than 10? */
206                 if (value % 10 == 0 && tu > 10)
207                         sprintf(buf, "%s hundred %s",
208                                 small[value/100], big[tu/10]);
209                 else if (tu < 20)
210                         sprintf(buf, "%s hundred and %s",
211                                 small[value/100], small[tu]);
212                 else
213                         sprintf(buf, "%s hundred %s %s",
214                                 small[value/100], big[tu/10], small[tu % 10]);
215         }
216         else
217         {
218                 /* is it an even multiple of 10 other than 10? */
219                 if (value % 10 == 0 && tu > 10)
220                         sprintf(buf, "%s", big[tu/10]);
221                 else if (tu < 20)
222                         sprintf(buf, "%s", small[tu]);
223                 else
224                         sprintf(buf, "%s %s", big[tu/10], small[tu % 10]);
225         }
226
227         return(buf);
228 }
229
230 /* this converts a long as well but to a representation using words */
231 /* obviously way North American centric - sorry */
232 const char *
233 cash_words_out(long value)
234 {
235         static char     buf[128];
236         char    *p = buf;
237         long    m0;
238         long    m1;
239         long    m2;
240         long    m3;
241
242         /* work with positive numbers */
243         if (value < 0)
244         {
245                 value *= -1;
246                 strcpy(buf, "minus ");
247                 p += 6;
248         }
249         else
250                 *buf = 0;
251
252         m0 = value % 100;                               /* cents */
253         m1 = (value/100) % 1000;                /* hundreds */
254         m2 = (value/100000) % 1000;             /* thousands */
255         m3 = value/100000000 % 1000;    /* millions */
256
257         if (m3)
258         {
259                 strcat(buf, num_word(m3));
260                 strcat(buf, " million ");
261         }
262
263         if (m2)
264         {
265                 strcat(buf, num_word(m2));
266                 strcat(buf, " thousand ");
267         }
268
269         if (m1)
270                 strcat(buf, num_word(m1));
271
272         if (!*p)
273                 strcat(buf, "zero");
274
275         strcat(buf, (int)(value/100) == 1 ? " dollar and " : " dollars and ");
276         strcat(buf, num_word(m0));
277         strcat(buf, m0 == 1 ? " cent" : " cents");
278         *buf = toupper(*buf);
279         return(buf);
280 }
281
282 #include        <stdlib.h>
283
284 #ifdef  TEST_MAIN
285 int
286 main(void)
287 {
288         char    p[64], *q;
289         long    v;                      /* the long value representing the amount */
290         int             d;                      /* number of decimal places - default 2 */
291
292         while (fgets(p, sizeof(p), stdin) != NULL)
293         {
294                 if ((q = strchr(p, '\n')) != NULL)
295                         *q = 0;
296
297                 for (q = p; *q && !isspace(*q); q++)
298                         ;
299
300                 v = cash_in(p);
301                 d = *q ? atoi(q) : 2;
302
303                 printf("%12.12s %10ld ", p, v);
304                 printf("%12s %s\n", cash_out(v), cash_words_out(v));
305         }
306
307         return(0);
308 }
309 #endif  /* TEST_MAIN */