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