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 int4s
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.9 1997/08/22 07:12:52 momjian 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 int4 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 == '0' || *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);
375 elog(WARN,"cash_div: divide by 0.0 error");
377 *result = rint(*c / *f);
384 * Return larger of two cash values.
387 cashlarger(Cash *c1, Cash *c2)
391 if (!PointerIsValid(c1) || !PointerIsValid(c2))
394 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
395 elog(WARN,"Memory allocation failed, can't return larger cash",NULL);
397 *result = ((*c1 > *c2)? *c1: *c2);
404 * Return smaller of two cash values.
407 cashsmaller(Cash *c1, Cash *c2)
411 if (!PointerIsValid(c1) || !PointerIsValid(c2))
414 if (!PointerIsValid(result = PALLOCTYPE(Cash)))
415 elog(WARN,"Memory allocation failed, can't return smaller cash",NULL);
417 *result = ((*c1 < *c2)? *c1: *c2);
420 } /* cashsmaller() */
424 * This converts a int4 as well but to a representation using words
425 * Obviously way North American centric - sorry
428 cash_words_out(Cash *value)
430 static char buf[128];
437 /* work with positive numbers */
440 strcpy(buf, "minus ");
446 m0 = *value % 100; /* cents */
447 m1 = (*value/100) % 1000; /* hundreds */
448 m2 = (*value/100000) % 1000; /* thousands */
449 m3 = *value/100000000 % 1000; /* millions */
452 strcat(buf, num_word(m3));
453 strcat(buf, " million ");
457 strcat(buf, num_word(m2));
458 strcat(buf, " thousand ");
462 strcat(buf, num_word(m1));
467 strcat(buf, (int)(*value/100) == 1 ? " dollar and " : " dollars and ");
468 strcat(buf, num_word(m0));
469 strcat(buf, m0 == 1 ? " cent" : " cents");
470 *buf = toupper(*buf);
472 } /* cash_words_out() */
475 /*************************************************************************
477 ************************************************************************/
482 static char buf[128];
483 static const char *small[] = {
484 "zero", "one", "two", "three", "four", "five", "six", "seven",
485 "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
486 "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty",
487 "thirty", "fourty", "fifty", "sixty", "seventy", "eighty", "ninety"
489 const char **big = small + 18;
490 int tu = value % 100;
492 /* deal with the simple cases first */
494 return(small[value]);
496 /* is it an even multiple of 100? */
498 sprintf(buf, "%s hundred", small[value/100]);
504 /* is it an even multiple of 10 other than 10? */
505 if (value % 10 == 0 && tu > 10)
506 sprintf(buf, "%s hundred %s",
507 small[value/100], big[tu/10]);
509 sprintf(buf, "%s hundred and %s",
510 small[value/100], small[tu]);
512 sprintf(buf, "%s hundred %s %s",
513 small[value/100], big[tu/10], small[tu % 10]);
516 /* is it an even multiple of 10 other than 10? */
517 if (value % 10 == 0 && tu > 10)
518 sprintf(buf, "%s", big[tu/10]);
520 sprintf(buf, "%s", small[tu]);
522 sprintf(buf, "%s %s", big[tu/10], small[tu % 10]);