3 * Written by D'Arcy J.M. Cain
5 * Functions to allow input and output of money normally but store
6 * and handle it as longs
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.
12 * $Header: /cvsroot/pgsql/src/backend/utils/adt/cash.c,v 1.6 1997/04/24 20:30:41 scrappy Exp $
22 #include "miscadmin.h"
23 #include "utils/builtins.h"
24 #include "utils/cash.h"
26 static const char *num_word(Cash value);
28 /* when we go to 64 bit values we will have to modify this */
31 #define TERMINATOR (CASH_BUFSZ - 1)
32 #define LAST_PAREN (TERMINATOR - 1)
33 #define LAST_DIGIT (LAST_PAREN - 1)
36 static struct lconv *lconv = NULL;
40 * Convert a string to a cash data type.
41 * Format is [$]###[,]###[.##]
42 * Examples: 123.45 $123.45 $123,456.78
44 * This is currently implemented as a 32-bit integer.
45 * XXX HACK It looks as though some of the symbols for
46 * monetary values returned by localeconv() can be multiple
47 * bytes/characters. This code assumes one byte only. - tgl 97/04/14
50 cash_in(const char *str)
60 char dsymbol, ssymbol, psymbol, nsymbol, csymbol;
63 if (lconv == NULL) lconv = localeconv();
65 /* frac_digits in the C locale seems to return CHAR_MAX */
66 /* best guess is 2 in this case I think */
67 fpoint = ((lconv->frac_digits != CHAR_MAX)? lconv->frac_digits: 2); /* int_frac_digits? */
69 dsymbol = *lconv->mon_decimal_point;
70 ssymbol = *lconv->mon_thousands_sep;
71 csymbol = *lconv->currency_symbol;
72 psymbol = *lconv->positive_sign;
73 nsymbol = *lconv->negative_sign;
83 /* we need to add all sorts of checking here. For now just */
84 /* strip all leading whitespace and any leading dollar sign */
85 while (isspace(*s) || *s == csymbol) s++;
87 /* a leading minus or paren signifies a negative number */
88 /* again, better heuristics needed */
89 if (*s == nsymbol || *s == '(') {
93 } else if (*s == psymbol) {
97 while (isspace(*s) || *s == csymbol) s++;
100 /* we look for digits as long as we have less */
101 /* than the required number of decimal places */
102 if (isdigit(*s) && dec < fpoint) {
103 value = (value * 10) + *s - '0';
108 /* decimal point? then start counting fractions... */
109 } else if (*s == dsymbol && !seen_dot) {
112 /* "thousands" separator? then skip... */
113 } else if (*s == ssymbol) {
117 if (isdigit(*s) && *s >= '5')
120 /* adjust for less than required decimal places */
121 for (; dec < fpoint; dec++)
128 while (isspace(*s) || *s == ')') s++;
131 elog(WARN,"Bad money external representation %s",str);
133 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
134 elog(WARN,"Memory allocation failed, can't input cash '%s'",str);
136 *result = (value * sgn);
143 * Function to convert cash to a dollars and cents representation.
144 * XXX HACK This code appears to assume US conventions for
145 * positive-valued amounts. - tgl 97/04/14
148 cash_out(Cash *value)
151 char buf[CASH_BUFSZ];
153 int count = LAST_DIGIT;
155 int comma_position = 0;
156 char mon_group, comma, points;
157 char csymbol, dsymbol, *nsymbol;
161 if (lconv == NULL) lconv = localeconv();
163 mon_group = *lconv->mon_grouping;
164 comma = *lconv->mon_thousands_sep;
165 csymbol = *lconv->currency_symbol;
166 dsymbol = *lconv->mon_decimal_point;
167 nsymbol = lconv->negative_sign;
168 /* frac_digits in the C locale seems to return CHAR_MAX */
169 /* best guess is 2 in this case I think */
170 points = ((lconv->frac_digits != CHAR_MAX)? lconv->frac_digits: 2); /* int_frac_digits? */
171 convention = lconv->n_sign_posn;
182 point_pos = LAST_DIGIT - points;
184 /* We're playing a little fast and loose with this. Shoot me. */
185 if (!mon_group || mon_group == CHAR_MAX)
188 /* allow more than three decimal points and separate them */
190 point_pos -= (points - 1)/mon_group;
191 comma_position = point_pos % (mon_group + 1);
194 /* we work with positive amounts and add the minus sign at the end */
200 /* allow for trailing negative strings */
201 memset(buf, ' ', CASH_BUFSZ);
202 buf[TERMINATOR] = buf[LAST_PAREN] = '\0';
204 while (*value || count > (point_pos - 2)) {
205 if (points && count == point_pos)
206 buf[count--] = dsymbol;
207 else if (comma && count % (mon_group + 1) == comma_position)
208 buf[count--] = comma;
210 buf[count--] = (*value % 10) + '0';
214 buf[count] = csymbol;
216 if (buf[LAST_DIGIT] == ',')
217 buf[LAST_DIGIT] = buf[LAST_PAREN];
219 /* see if we need to signify negative amount */
221 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count + strlen(nsymbol))))
222 elog(WARN,"Memory allocation failed, can't output cash",NULL);
224 /* Position code of 0 means use parens */
226 sprintf(result, "(%s)", buf + count);
227 else if (convention == 2)
228 sprintf(result, "%s%s", buf + count, nsymbol);
230 sprintf(result, "%s%s", nsymbol, buf + count);
232 if (!PointerIsValid(result = PALLOC(CASH_BUFSZ + 2 - count)))
233 elog(WARN,"Memory allocation failed, can't output cash",NULL);
235 strcpy(result, buf + count);
243 cash_eq(Cash *c1, Cash *c2)
245 if (!PointerIsValid(c1) || !PointerIsValid(c2))
252 cash_ne(Cash *c1, Cash *c2)
254 if (!PointerIsValid(c1) || !PointerIsValid(c2))
261 cash_lt(Cash *c1, Cash *c2)
263 if (!PointerIsValid(c1) || !PointerIsValid(c2))
270 cash_le(Cash *c1, Cash *c2)
272 if (!PointerIsValid(c1) || !PointerIsValid(c2))
279 cash_gt(Cash *c1, Cash *c2)
281 if (!PointerIsValid(c1) || !PointerIsValid(c2))
288 cash_ge(Cash *c1, Cash *c2)
290 if (!PointerIsValid(c1) || !PointerIsValid(c2))
298 * Add two cash values.
301 cash_pl(Cash *c1, Cash *c2)
305 if (!PointerIsValid(c1) || !PointerIsValid(c2))
308 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
309 elog(WARN,"Memory allocation failed, can't add cash",NULL);
311 *result = (*c1 + *c2);
318 * Subtract two cash values.
321 cash_mi(Cash *c1, Cash *c2)
325 if (!PointerIsValid(c1) || !PointerIsValid(c2))
328 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
329 elog(WARN,"Memory allocation failed, can't subtract cash",NULL);
331 *result = (*c1 - *c2);
338 * Multiply cash by floating point number.
341 cash_mul(Cash *c, float8 *f)
345 if (!PointerIsValid(f) || !PointerIsValid(c))
348 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
349 elog(WARN,"Memory allocation failed, can't multiply cash",NULL);
351 *result = ((*f) * (*c));
358 * Divide cash by floating point number.
360 * XXX Don't know if rounding or truncating is correct behavior.
361 * Round for now. - tgl 97/04/15
364 cash_div(Cash *c, float8 *f)
368 if (!PointerIsValid(f) || !PointerIsValid(c))
371 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
372 elog(WARN,"Memory allocation failed, can't divide cash",NULL);
374 *result = rint(*c / *f);
381 * Return larger of two cash values.
384 cashlarger(Cash *c1, Cash *c2)
388 if (!PointerIsValid(c1) || !PointerIsValid(c2))
391 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
392 elog(WARN,"Memory allocation failed, can't return larger cash",NULL);
394 *result = ((*c1 > *c2)? *c1: *c2);
401 * Return smaller of two cash values.
404 cashsmaller(Cash *c1, Cash *c2)
408 if (!PointerIsValid(c1) || !PointerIsValid(c2))
411 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
412 elog(WARN,"Memory allocation failed, can't return smaller cash",NULL);
414 *result = ((*c1 < *c2)? *c1: *c2);
417 } /* cashsmaller() */
421 * This converts a long as well but to a representation using words
422 * Obviously way North American centric - sorry
425 cash_words_out(Cash *value)
427 static char buf[128];
434 /* work with positive numbers */
437 strcpy(buf, "minus ");
443 m0 = *value % 100; /* cents */
444 m1 = (*value/100) % 1000; /* hundreds */
445 m2 = (*value/100000) % 1000; /* thousands */
446 m3 = *value/100000000 % 1000; /* millions */
449 strcat(buf, num_word(m3));
450 strcat(buf, " million ");
454 strcat(buf, num_word(m2));
455 strcat(buf, " thousand ");
459 strcat(buf, num_word(m1));
464 strcat(buf, (int)(*value/100) == 1 ? " dollar and " : " dollars and ");
465 strcat(buf, num_word(m0));
466 strcat(buf, m0 == 1 ? " cent" : " cents");
467 *buf = toupper(*buf);
469 } /* cash_words_out() */
472 /*************************************************************************
474 ************************************************************************/
479 static char buf[128];
480 static const char *small[] = {
481 "zero", "one", "two", "three", "four", "five", "six", "seven",
482 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
483 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
484 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
486 const char **big = small + 18;
487 int tu = value % 100;
489 /* deal with the simple cases first */
491 return(small[value]);
493 /* is it an even multiple of 100? */
495 sprintf(buf, "%s hundred", small[value/100]);
501 /* is it an even multiple of 10 other than 10? */
502 if (value % 10 == 0 && tu > 10)
503 sprintf(buf, "%s hundred %s",
504 small[value/100], big[tu/10]);
506 sprintf(buf, "%s hundred and %s",
507 small[value/100], small[tu]);
509 sprintf(buf, "%s hundred %s %s",
510 small[value/100], big[tu/10], small[tu % 10]);
513 /* is it an even multiple of 10 other than 10? */
514 if (value % 10 == 0 && tu > 10)
515 sprintf(buf, "%s", big[tu/10]);
517 sprintf(buf, "%s", small[tu]);
519 sprintf(buf, "%s %s", big[tu/10], small[tu % 10]);