3 Written by D'Arcy J.M. Cain
5 Functions to allow input and output of money normally but store
8 Set tabstops to 4 for best results
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.
24 # define palloc malloc
31 /* when we go to 64 bit values we will have to modify this */
34 #define TERMINATOR (CASH_BUFSZ - 1)
35 #define LAST_PAREN (TERMINATOR - 1)
36 #define LAST_DIGIT (LAST_PAREN - 1)
38 /* function to convert a long to a dollars and cents representation */
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? */
48 int count = LAST_DIGIT;
50 int comma_position = 0;
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)
57 point_pos = LAST_DIGIT - points;
59 /* We're playing a little fast and loose with this. Shoot me. */
60 if (!mod_group || mod_group == CHAR_MAX)
63 /* allow more than three decimal points and separate them */
66 point_pos -= (points - 1)/mod_group;
67 comma_position = point_pos % (mod_group + 1);
70 /* we work with positive amounts and add the minus sign at the end */
77 /* allow for trailing negative strings */
78 memset(buf, ' ', CASH_BUFSZ);
79 buf[TERMINATOR] = buf[LAST_PAREN] = 0;
81 while (value || count > (point_pos - 2))
83 if (points && count == point_pos)
84 buf[count--] = *lc->decimal_point;
85 else if (comma && count % (mod_group + 1) == comma_position)
88 buf[count--] = (value % 10) + '0';
92 if (buf[LAST_DIGIT] == ',')
93 buf[LAST_DIGIT] = buf[LAST_PAREN];
95 /* see if we need to signify negative amount */
98 retbuf = palloc(CASH_BUFSZ + 2 - count + strlen(lc->negative_sign));
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);
106 sprintf(retbuf, "%s%s", lc->negative_sign, buf + count);
110 retbuf = palloc(CASH_BUFSZ + 2 - count);
111 strcpy(retbuf, buf + count);
117 /* convert a string to a long integer */
119 cash_in(const char *s)
125 struct lconv *lc = localeconv();
126 int fpoint = lc->frac_digits; /* int_frac_digits? */
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 == '$')
133 /* a leading minus or paren signifies a negative number */
134 /* again, better heuristics needed */
135 if (*s == '-' || *s == '(')
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)
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)
154 value = (value * 10) + *s - '0';
159 else if (*s == *lc->decimal_point && !seen_dot)
164 if (isdigit(*s) && *s >= '5')
167 /* adjust for less than required decimal places */
168 for (; dec < fpoint; dec++)
177 /* used by cash_words_out() below */
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"
188 const char **big = small + 18;
189 int tu = value % 100;
191 /* deal with the simple cases first */
193 return(small[value]);
195 /* is it an even multiple of 100? */
198 sprintf(buf, "%s hundred", small[value/100]);
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]);
210 sprintf(buf, "%s hundred and %s",
211 small[value/100], small[tu]);
213 sprintf(buf, "%s hundred %s %s",
214 small[value/100], big[tu/10], small[tu % 10]);
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]);
222 sprintf(buf, "%s", small[tu]);
224 sprintf(buf, "%s %s", big[tu/10], small[tu % 10]);
230 /* this converts a long as well but to a representation using words */
231 /* obviously way North American centric - sorry */
233 cash_words_out(long value)
235 static char buf[128];
242 /* work with positive numbers */
246 strcpy(buf, "minus ");
252 m0 = value % 100; /* cents */
253 m1 = (value/100) % 1000; /* hundreds */
254 m2 = (value/100000) % 1000; /* thousands */
255 m3 = value/100000000 % 1000; /* millions */
259 strcat(buf, num_word(m3));
260 strcat(buf, " million ");
265 strcat(buf, num_word(m2));
266 strcat(buf, " thousand ");
270 strcat(buf, num_word(m1));
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);
289 long v; /* the long value representing the amount */
290 int d; /* number of decimal places - default 2 */
292 while (fgets(p, sizeof(p), stdin) != NULL)
294 if ((q = strchr(p, '\n')) != NULL)
297 for (q = p; *q && !isspace(*q); q++)
301 d = *q ? atoi(q) : 2;
303 printf("%12.12s %10ld ", p, v);
304 printf("%12s %s\n", cash_out(v), cash_words_out(v));
309 #endif /* TEST_MAIN */